# How the `wg.Go` Pattern Differs from Manual WaitGroup Usage in Go 1.25

> Discover how Go 1.25's wg.Go pattern simplifies goroutine management. Learn its advantages over manual WaitGroup usage for cleaner, safer code.

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

---

**The `wg.Go` pattern introduced in Go 1.25 is a convenience wrapper that automatically handles `Add(1)` before launching a goroutine and `Done()` when it completes, eliminating the boilerplate and potential race conditions associated with manual `sync.WaitGroup` management.**

The `sync.WaitGroup` is a fundamental synchronization primitive for coordinating goroutines in Go. The `wg.Go` pattern, documented in the JetBrains/go-modern-guidelines repository, provides a safer and more concise alternative to manually calling `Add` and `Done`. This helper wraps the lifecycle management of tracked goroutines to prevent common concurrency bugs while reducing visual clutter in concurrent code.

## Manual `sync.WaitGroup` Usage

When managing goroutines manually, you must carefully coordinate three distinct operations: incrementing the counter before launch, ensuring decrementing on completion (usually via `defer`), and waiting for all work to finish. This pattern is error-prone; forgetting `Add` before `go`, calling `Add` after the goroutine starts, or missing `Done` results in deadlocks or premature `Wait` returns.

Here is the traditional manual implementation:

```go
var wg sync.WaitGroup
for _, item := range items {
    wg.Add(1)
    go func(i int) {
        defer wg.Done()
        process(i)
    }(item)
}
wg.Wait()

```

The boilerplate `wg.Add` and `defer wg.Done` statements clutter the core logic and require careful placement to avoid race conditions.

## The `wg.Go` Pattern in Go 1.25

Go 1.25 introduces the `wg.Go` method as a helper on `sync.WaitGroup`. According to the `go-modern-guidelines` source code, this pattern encapsulates the **Add-Run-Done** sequence into a single function call, ensuring the wait group counter is always correctly managed regardless of how the goroutine exits.

Here is the equivalent implementation using `wg.Go`:

```go
var wg sync.WaitGroup
for _, item := range items {
    wg.Go(func() {
        process(item) // no explicit Add/Done needed
    })
}
wg.Wait()

```

The helper internally performs `Add(1)` before starting the goroutine and guarantees `Done()` is called when the passed function returns.

## Key Differences and Safety Guarantees

### Lifecycle Management

**Manual approach:** You must ensure `Add` executes before the `go` statement and that `Done` is reachable via all exit paths (typically using `defer wg.Done()`). This creates a temporal coupling that is easy to violate during refactoring.

**`wg.Go` approach:** The method automatically increments the counter atomically before launching the goroutine and schedules `Done()` to run when the function returns, even if a panic occurs. This binds the lifecycle to a single call site.

### Error Prevention

Common bugs with manual `WaitGroup` usage include negative counter panics (calling `Done` too many times) or indefinite waits (forgetting `Done` entirely). Because `wg.Go` controls both sides of the lifecycle internally, it eliminates the risk of mismatched `Add`/`Done` calls that lead to synchronization failures.

### Code Clarity

The manual pattern requires three lines of boilerplate for every goroutine launch. The `wg.Go` pattern expresses the same intent in one line, making the actual business logic (`process(item)`) the visual focus rather than the synchronization mechanics.

## Implementation in the go-modern-guidelines Repository

The formal definition of this pattern resides in the JetBrains/go-modern-guidelines repository. The guideline entry `"sync_waitgroup_go"` in [`internal/guidelines/guidelines.json`](https://github.com/JetBrains/go-modern-guidelines/blob/main/internal/guidelines/guidelines.json) (line 887) provides the canonical description and examples for automated tooling detection.

Supporting documentation appears in [`FEATURES.md`](https://github.com/JetBrains/go-modern-guidelines/blob/main/FEATURES.md) around line 562, which lists `wg.Go` among modern Go features available in version 1.25. The test suite in [`internal/guidelines/guidelines_test.go`](https://github.com/JetBrains/go-modern-guidelines/blob/main/internal/guidelines/guidelines_test.go) verifies that these documentation strings parse correctly for static analysis integration.

## When to Use `wg.Go`

Adopt the `wg.Go` pattern whenever you use a `sync.WaitGroup` to track goroutine completion. It is particularly valuable in scenarios such as:

- **Worker pools** where tight loops spawn many goroutines
- **Fan-out patterns** requiring guaranteed synchronization
- **Complex error handling** where `defer wg.Done()` might be accidentally omitted in an early return path

The pattern is appropriate for any goroutine whose lifetime must be accounted for by a `WaitGroup`, offering exactly the same functionality as manual management with improved safety.

## Summary

- **`wg.Go`** is a Go 1.25 helper method on `sync.WaitGroup` that wraps the `Add`-`Run`-`Done` sequence
- It **automatically increments** the counter before launching the goroutine and **guarantees** `Done()` is called on completion
- The pattern **eliminates common pitfalls** such as forgetting `Add`, calling `Add` too late, or missing `Done`
- Defined in the `go-modern-guidelines` repository at [`internal/guidelines/guidelines.json`](https://github.com/JetBrains/go-modern-guidelines/blob/main/internal/guidelines/guidelines.json) under the `"sync_waitgroup_go"` entry
- Listed in [`FEATURES.md`](https://github.com/JetBrains/go-modern-guidelines/blob/main/FEATURES.md) as a modern Go feature for version 1.25

## Frequently Asked Questions

### What Go version introduced the `wg.Go` pattern?

The `wg.Go` pattern was introduced in **Go 1.25** as a convenience method on the standard library's `sync.WaitGroup` type. The JetBrains/go-modern-guidelines repository documents this feature in [`FEATURES.md`](https://github.com/JetBrains/go-modern-guidelines/blob/main/FEATURES.md) as part of the modern Go feature set for that release.

### Does `wg.Go` handle panics differently than manual `defer wg.Done()`?

No special panic handling is required because `wg.Go` internally uses a `defer` statement to ensure `Done()` is called. This guarantees the wait group counter is decremented even if the passed function panics, preventing indefinite hangs that could occur if manual `Done()` calls were skipped during a panic recovery.

### Can I migrate existing manual `WaitGroup` code to use `wg.Go`?

Yes. Any existing code using `sync.WaitGroup` can adopt `wg.Go` by removing the explicit `wg.Add(1)` before the goroutine and removing the `defer wg.Done()` (or equivalent) inside the goroutine. The logic inside the goroutine remains unchanged; only the launch mechanism is simplified to `wg.Go(func() { ... })`.

### Where is the official guideline for `wg.Go` documented?

The canonical guideline is defined in the JetBrains/go-modern-guidelines repository within [`internal/guidelines/guidelines.json`](https://github.com/JetBrains/go-modern-guidelines/blob/main/internal/guidelines/guidelines.json) at the `"sync_waitgroup_go"` entry (line 887). This JSON entry provides the description, examples, and metadata used by JetBrains tooling to detect and suggest this pattern in Go codebases.