Infrastructure Go daemon
SECTION 6.3

WaitGroups: Knowing When It's Done

sync.WaitGroup tracks a count of goroutines and blocks until all are done.

2 · Worked example — read every step
import "sync"

var wg sync.WaitGroup

urls := []string{"https://api-1.example.com", "https://api-2.example.com", "https://api-3.example.com"}

for _, url := range urls {
    wg.Add(1)
    go func(u string) {
        defer wg.Done()
        resp, err := http.Get(u)
        if err != nil {
            fmt.Printf("%s: error %v\n", u, err)
            return
        }
        resp.Body.Close()
        fmt.Printf("%s: %d\n", u, resp.StatusCode)
    }(url)
}

wg.Wait() // blocks until all goroutines call Done()
fmt.Println("all checks complete")

The Pattern

  1. wg.Add(1) before launching the goroutine
  2. defer wg.Done() as the first line inside the goroutine
  3. wg.Wait() after the loop

Gotcha: Call Add(1) before go func(), not inside it. Otherwise there's a race between Wait() and Add().

Goroutine Leak

// BAD: this goroutine never exits if nobody reads from ch
go func() {
    result := expensiveWork()
    ch <- result  // blocks forever if nobody reads
}()
// If the caller gives up (timeout), this goroutine leaks

Every goroutine you launch must have a way to exit. Channels, context cancellation, or WaitGroups — use at least one.

3 · Fill the gaps
The three-call pattern, from memory — and mind where each call goes.
var wg sync.WaitGroup

for _, url := range urls { wg. // before the launch, not inside it go func(u string) { defer wg. check(u) }(url) }

wg. fmt.Println("all checks complete")

4 · From scratch — this feeds your review queue