# WaitGroup.Go Pattern vs Manual Add/Done in Go: A Safety Comparison

> Explore the WaitGroup Go pattern vs manual Add Done for safer Go concurrency. Learn how WaitGroup Go prevents race conditions and boilerplate for streamlined parallel execution.

- Repository: [JetBrains/go-modern-guidelines](https://github.com/jetbrains/go-modern-guidelines)
- Tags: deep-dive
- Published: 2026-09-04

---

**The `WaitGroup.Go` pattern eliminates race conditions and boilerplate by automatically pairing `Add(1)` with `Done()` inside a helper method, making it a safer alternative to manual `sync.WaitGroup` management.**

The JetBrains/go-modern-guidelines repository explicitly recommends using a `Go` method wrapper for `sync.WaitGroup` through guideline ID `sync_waitgroup_go`, which advises developers to prefer this pattern when spawning tracked goroutines. This modern approach addresses the inherent fragility of manually calling `Add` before and `Done` after goroutine execution, as documented in the repository's test suite and guideline definitions.

## The Risks of Manual Add/Done Management

Traditional `sync.WaitGroup` usage requires careful coordination between three operations: incrementing the counter, spawning the goroutine, and signaling completion. In [`internal/guidelines/guidelines_test.go`](https://github.com/JetBrains/go-modern-guidelines/blob/main/internal/guidelines/guidelines_test.go), the test `TestListForResolvedVersion` validates the existence of guideline `sync_waitgroup_go`, which specifically targets the common errors in this manual pattern.

When managing goroutines manually, developers must call `wg.Add(1)` before the `go` statement to avoid race conditions where the goroutine finishes before the counter increments. Additionally, every goroutine must execute `wg.Done()` exactly once, typically via `defer wg.Done()`, or risk deadlocking the main goroutine. The JetBrains guidelines highlight that forgetting either call or placing `Add` after the goroutine starts creates race conditions where `Wait` returns prematurely or hangs indefinitely.

## Understanding the WaitGroup.Go Pattern

The `WaitGroup.Go` pattern encapsulates the boilerplate of goroutine tracking into a single method call. As defined in the repository's guideline system (rendered via [`internal/guidelines/guidelines.go`](https://github.com/JetBrains/go-modern-guidelines/blob/main/internal/guidelines/guidelines.go) lines 63-71), this pattern implements a helper method that internally calls `Add(1)`, spawns the goroutine, and guarantees `Done` is called via `defer` when the function returns.

This approach ensures that the counter increment happens atomically before goroutine launch and that completion is signaled even if the wrapped function panics. The guideline data stored in [`guidelines.json`](https://github.com/JetBrains/go-modern-guidelines/blob/main/guidelines.json) specifies this as the idiomatic replacement for manual synchronization management.

## Code Comparison: Manual vs WaitGroup.Go

### Manual Add/Done (Error-Prone)

The traditional approach requires explicit counter management at every spawn point:

```go
var wg sync.WaitGroup

for i := 0; i < 3; i++ {
    wg.Add(1)               // Must execute before go statement
    go func(id int) {
        defer wg.Done()     // Must execute exactly once
        // Work here
    }(i)
}
wg.Wait()

```

Critical failure modes include placing `wg.Add` after the `go` statement (causing `Wait` to return before workers finish) or omitting `defer wg.Done()` (causing deadlock). These errors are difficult to debug in production concurrency scenarios.

### WaitGroup.Go Pattern (Recommended)

Implementing the `Go` method creates a type-safe wrapper around `sync.WaitGroup`:

```go
type WaitGroup struct {
    sync.WaitGroup
}

func (wg *WaitGroup) Go(fn func()) {
    wg.Add(1)
    go func() {
        defer wg.Done()
        fn()
    }()
}

```

Usage becomes concise and foolproof:

```go
var wg WaitGroup

for i := 0; i < 3; i++ {
    id := i
    wg.Go(func() {
        // Work here
    })
}
wg.Wait()

```

## Safety Guarantees in the JetBrains Guidelines

The `sync_waitgroup_go` guideline documented in [`internal/guidelines/guidelines_test.go`](https://github.com/JetBrains/go-modern-guidelines/blob/main/internal/guidelines/guidelines_test.go) (lines 13-16) explicitly states: **"Use `wg.Go` when spawning goroutines tracked by a `sync.WaitGroup`."** This recommendation appears in the guideline list generated from [`guidelines.json`](https://github.com/JetBrains/go-modern-guidelines/blob/main/guidelines.json) and rendered by the logic in [`internal/guidelines/guidelines.go`](https://github.com/JetBrains/go-modern-guidelines/blob/main/internal/guidelines/guidelines.go).

The pattern provides three key safety guarantees:

- **Atomic pairing**: `Add(1)` always executes before the goroutine launches
- **Panic safety**: `defer wg.Done()` ensures the counter decrements even if `fn` panics
- **Boilerplate elimination**: Reduces three lines of error-prone code to one expressive call

## Summary

- The manual `Add`/`Done` pattern requires precise ordering and pairing that is easy to violate, leading to race conditions or deadlocks.
- The `WaitGroup.Go` pattern, as specified in JetBrains/go-modern-guidelines, encapsulates counter management in a method that guarantees correct `Add`/`Done` pairing.
- Implementation files [`internal/guidelines/guidelines_test.go`](https://github.com/JetBrains/go-modern-guidelines/blob/main/internal/guidelines/guidelines_test.go) and [`internal/guidelines/guidelines.go`](https://github.com/JetBrains/go-modern-guidelines/blob/main/internal/guidelines/guidelines.go) codify this as guideline `sync_waitgroup_go`.
- This pattern eliminates the race condition risk of late `Add` calls and the deadlock risk of missing `Done` calls.
- The helper method improves code readability by expressing intent ("track this goroutine") rather than mechanism (counter manipulation).

## Frequently Asked Questions

### What is the WaitGroup.Go pattern in Go?

The `WaitGroup.Go` pattern is a wrapper method that encapsulates `sync.WaitGroup` counter management by automatically calling `Add(1)` before spawning a goroutine and `defer Done()` inside it. As documented in the JetBrains/go-modern-guidelines repository under guideline ID `sync_waitgroup_go`, this pattern replaces the manual three-step process of `Add`, `go`, and `Done` with a single method call that prevents synchronization errors.

### Why is manual Add/Done error-prone?

Manual management requires placing `wg.Add(1)` strictly before the `go` statement and ensuring `wg.Done()` executes exactly once per goroutine. According to the JetBrains guidelines analysis, common mistakes include calling `Add` after the goroutine starts (creating a race where `Wait` returns early) or forgetting `Done` (causing permanent deadlock). These errors are syntactically valid but logically fatal in concurrent programs.

### How does WaitGroup.Go prevent race conditions?

The `WaitGroup.Go` helper ensures `Add(1)` executes in the calling goroutine before the new goroutine begins, eliminating the window where a fast-completing goroutine could decrement the counter before it increments. The implementation specified in the `sync_waitgroup_go` guideline guarantees the counter is always greater than zero when `Wait` is called, provided all goroutines use the helper method consistently.

### Is WaitGroup.Go part of the Go standard library?

No, `WaitGroup.Go` is not in the standard library `sync` package. It is a community pattern recommended by JetBrains/go-modern-guidelines that developers implement as a wrapper type. The guideline system in [`internal/guidelines/guidelines.go`](https://github.com/JetBrains/go-modern-guidelines/blob/main/internal/guidelines/guidelines.go) renders this recommendation from [`guidelines.json`](https://github.com/JetBrains/go-modern-guidelines/blob/main/guidelines.json), encouraging teams to adopt this helper to reduce concurrency bugs despite requiring a small custom implementation.