Your code deadlocked because of a subtle math error in the loop:
for i := 1; i <= 5; i++ {
wg.Add(i) // ❌ Adds 1, then 2, then 3, then 4, then 5 = 15 total
go worker(i, &wg) // But only spawns 5 goroutines
}
Counter state: 1 + 2 + 3 + 4 + 5 = 15
Done() calls: 5 workers × 1 = 5
Result: Counter sits at 10. Wait() blocks forever. Deadlock.
The fix:
for i := 1; i <= 5; i++ {
wg.Add(1) // ✅ Increment by 1 each time
go worker(i, &wg)
}
Now the counter is 5, and 5 workers call Done() → counter reaches 0 → Wait() unblocks.
Think of WaitGroup as a semaphore counter with three operations:
| Operation | Effect | When to Use |
|---|---|---|
Add(n) |
Increment counter by n | Before spawning goroutines |
Done() |
Decrement by 1 (alias for Add(-1)) |
When goroutine finishes |
Wait() |
Block until counter = 0 | In main or coordinating thread |
Key invariant: Counter must never go negative, or panic.
1. Call Add() before go