Go

Find the two WaitGroup bugs.

Question 495MediumGo 1.22 to 1.25
func worker(id int, wg sync.WaitGroup) { // BUG 2: copied
    defer wg.Done()
    fmt.Println("worker", id)
}

func main() {
    var wg sync.WaitGroup
    for i := range 3 {
        go func() {
            wg.Add(1) // BUG 1: Add inside the goroutine
            defer wg.Done()
            fmt.Println(i)
        }()
    }
    wg.Wait() // may return immediately

    for i := range 3 {
        wg.Add(1)
        go worker(i, wg) // Done runs on a copy, so Wait blocks forever
    }
    wg.Wait() // fatal error: all goroutines are asleep - deadlock!
}

Bug 1: Add must happen before the go statement, in the goroutine that later calls Wait. Otherwise Wait can run while the counter is still 0, return, and main exits before anything prints. Bug 2: WaitGroup contains a noCopy guard. Passing it by value means Done decrements a copy, the original never reaches zero, and the program deadlocks. go vet flags this. Pass *sync.WaitGroup, or better, let a closure capture it.

Go 1.25 added wg.Go(func()), which calls Add and Done for you and makes both bugs impossible. For error propagation and cancellation, use errgroup.Group.

More on Tricky Output & Code-Review Puzzles

All 38 Tricky Output & Code-Review Puzzles questions