How the `wg.Go` Pattern Differs from Manual WaitGroup Usage in Go 1.25
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:
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:
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 (line 887) provides the canonical description and examples for automated tooling detection.
Supporting documentation appears in 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 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.Gois a Go 1.25 helper method onsync.WaitGroupthat wraps theAdd-Run-Donesequence- 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, callingAddtoo late, or missingDone - Defined in the
go-modern-guidelinesrepository atinternal/guidelines/guidelines.jsonunder the"sync_waitgroup_go"entry - Listed in
FEATURES.mdas 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 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 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →