Go

What is the classic sync.WaitGroup bug, and what does wg.Go (Go 1.25) change?

Question 225MediumGo 1.22 to 1.25

The classic bug is calling Add inside the goroutine:

// BUG: Wait may run before any Add, and return immediately
for _, j := range jobs {
	go func() {
		wg.Add(1)
		defer wg.Done()
		process(j)
	}()
}
wg.Wait()

Add must happen before the go statement, so that it happens-before Wait. Other rules:

  • A negative counter panics.
  • You may reuse a WaitGroup only after the previous Wait has returned.
  • Pass it by pointer, never by value.

Go 1.25 added WaitGroup.Go, which does Add(1), starts the goroutine, and defers Done for you:

var wg sync.WaitGroup
for _, j := range jobs {
	wg.Go(func() { process(j) })
}
wg.Wait()

This removes the misplaced-Add and forgotten-Done bugs. Go 1.25's go vet also has a waitgroup analyzer that flags wg.Add(1) inside the goroutine.

What WaitGroup does not do: it does not collect errors or cancel the other goroutines. When you need either, use errgroup.

More on Concurrency Patterns & sync

All 38 Concurrency Patterns & sync questions