# sync.WaitGroup — Go Concurrency Patterns

Source: https://www.skillbyai.com/en/go-concurrency/gc-waitgroup

> Waiting for a set of goroutines to finish.

## Add before go, Done on exit, Wait once

A `sync.WaitGroup` is a counter: `Add(n)` increases it, `Done()` decreases it by one, and `Wait()` blocks until it reaches zero. The key rule is to call **`Add` before starting the goroutine**, in the parent; calling `Add` inside the new goroutine races with `Wait`, which may see zero and return early. Put `defer wg.Done()` at the top of the goroutine so it runs even on early return. Pass the WaitGroup by pointer, never by value (copying it breaks it; `go vet` reports copied locks). Go 1.25 added `wg.Go(f)`, which does the `Add(1)`, starts `f` in a goroutine and calls `Done` when it returns (check that your Go version has it). A WaitGroup does not collect errors or cancel siblings; use `errgroup` for that.

## Tools from the sync package

Waiting for groups, protecting shared state and doing things once, with low-level atomics underneath.

![Three ideas: WaitGroup, Mutex and RWMutex, and Once, atomics and sync.Map.](assets/figures/go-concurrency/section-4-map.svg) — Figure 4.1: goroutines coordinating through sync primitives.

## Classic form and the newer wg.Go form

Both wait for all workers; wg.Go requires Go 1.25 or later.

```go
package main

import (
	"fmt"
	"sync"
)

func main() {
	urls := []string{"a.example", "b.example", "c.example"}

	// Classic: Add in the parent, Done deferred in the child.
	var wg sync.WaitGroup
	for _, u := range urls {
		wg.Add(1)
		go func() {
			defer wg.Done()
			fmt.Println("fetch", u) // Go 1.22+: u is per-iteration
		}()
	}
	wg.Wait()

	// Go 1.25+: wg.Go wraps Add, go and Done.
	var wg2 sync.WaitGroup
	for _, u := range urls {
		wg2.Go(func() {
			fmt.Println("fetch again", u)
		})
	}
	wg2.Wait()
}
```

## Loop variables before Go 1.22

Before Go 1.22, a `for` loop variable was shared across iterations, so closures often all saw the last value. Since Go 1.22 (when the module go line is 1.22 or later) each iteration gets a fresh variable. In older modules, pass the value as an argument: `go func(u string) { ... }(u)`.

**Quiz:** Where should wg.Add(1) be called?

- [x] In the parent goroutine, before the go statement
- [ ] Inside the new goroutine, as its first line
- [ ] After wg.Wait()
- [ ] Inside a deferred function

*Answer:* In the parent goroutine, before the go statement. Adding inside the goroutine races with Wait, which may return before the counter is incremented.
