Go

What does this counter print, and how do you prove the bug?

Question 511MediumGo 1.22 to 1.25
func main() {
    var n int
    var wg sync.WaitGroup
    for range 1000 {
        wg.Add(1)
        go func() {
            defer wg.Done()
            n++ // data race
        }()
    }
    wg.Wait()
    fmt.Println(n) // usually < 1000, varies by run
}

The output is undefined. On multi-core machines it is usually less than 1000 and changes from run to run. n++ is a read, an add, and a write. Two goroutines can read the same value, and one increment is lost. Formally, this is a data race, and the memory model gives no guarantees at all for racy programs. For a single aligned machine word the program will not crash in practice, but it reads stale values and loses updates. For multi-word values (strings, slices, interfaces), a race can produce torn values and real memory corruption.

Prove it with the race detector: go run -race main.go reports WARNING: DATA RACE with both stack traces. Run tests with -race in CI.

Fixes, from cheapest to most flexible:

var n atomic.Int64
n.Add(1)          // lock-free

var mu sync.Mutex
mu.Lock()
n++
mu.Unlock()       // for multi-field invariants

Or have each goroutine send its result on a channel and add them up in one goroutine. The interviewer wants you to avoid saying "it's fine because it's just an int" and to know that GOMAXPROCS=1 hides the race without fixing it.

More on Tricky Output & Code-Review Puzzles

All 38 Tricky Output & Code-Review Puzzles questions