Concurrency · 3 MIN READ
WaitGroup.Go joins tasks; it does not collect their errors
Use modern WaitGroup task ownership and explain its limits.
A request owns more than the act of starting goroutines. It must know when its work has stopped and who retains the results. Go 1.25 added WaitGroup.Go, which combines task registration and launch. It reduces a common Add/launch ordering mistake without supplying cancellation, an error result or a concurrency budget.
WaitGroup.Go joins tasks; it does not collect their errors · A MAP OF THE IDEA
- Register owned tasks
- Write independent results
- Observe cancellation
- Join before cleanup
Useful when
- Use modern WaitGroup task ownership and explain its limits.
- WaitGroup.Go registers owned work and Wait joins it. Cancellation, errors, panic policy and admission control remain separate parts of the design.
Watch out
- Use an error-aware group for returned task errors and keep a separate bounded cancellation path for work that may block.
Implementation & operation cost: Choose the policy from the actual contract, then verify its failure path in the target environment.
Register and join owned work
Each task writes to a distinct fixed result slot, and the owner reads results after Wait. That separates mutation by index without concurrently appending to a shared slice. Do not copy an already used WaitGroup or start an unrelated task after the owner has concluded that the group is empty.
package main
import (
"fmt"
"sync"
)
func main() {
input := []int{2, 3, 4}
output := make([]int, len(input))
var tasks sync.WaitGroup
for i, value := range input {
tasks.Go(func() { output[i] = value * value })
}
tasks.Wait()
fmt.Println(output)
}
// Output:
// [4 9 16]
This complete Go 1.25+ example relies on modern loop-declared variables, distinct output elements and an explicit join. It launches only three tasks. A production version with many inputs still needs admission control before launch, and result values must not share unsafe internal storage.
Do not infer an error protocol
The task function passed to WaitGroup.Go must not panic. It has no error return. Expected failures need an explicit result channel or an error-aware task group, and panic handling belongs to a deliberately chosen boundary. A WaitGroup cannot roll back side effects from a task that already ran.
Give cancellation its own path
Wait does not take a context or kill a blocked task. A task doing I/O or sending a result must observe cancellation at each potentially blocking point. On shutdown, stop intake, signal cancellation where appropriate, wait for owned tasks and only then close the resources they use.
Test completion rather than scheduling luck
Use synchronization or a virtual-time test to hold a task at a known boundary. Verify the owner does not publish success until all required work finishes. A sleep followed by checking a counter depends on timing and may hide the same bug on a slower machine.
TAKE THIS WITH YOU
WaitGroup.Go registers owned work and Wait joins it. Cancellation, errors, panic policy and admission control remain separate parts of the design.
Check your understanding
A WaitGroup task blocks forever on an uncancellable send. What does calling Wait do?
Go to the source
Progress stays in this browser. No account needed.