Why count++ loses updates in goroutines

Read the explanation
What if

Two goroutines each run count++ once. The result can be 1, not 2.

0123456789go g1go g2wg.Wait()main: print countread 0add → 1write 1read 0add → 1write 1

Tap any block to see what that thread is doing.

  • Running
  • Waiting / parked
Step 1/6

main starts two goroutines. Each runs count++ once.

1var count int2 3func increment() {4    count++ // read count, add 1, write count5}6 7func main() {8    var wg sync.WaitGroup9    for range 2 {10        wg.Add(1)11        go func() {12            defer wg.Done()13            increment()14        }()15    }16    wg.Wait()17    fmt.Println(count) // can print 118}

Explanation

What just happened

count++ looks like one step, but it’s three: read count, add 1, write it back. g2 read count before g1 wrote its new value, so both started from 0 and both wrote 1. One update was lost.

Our facts test runs a million increments in each of two goroutines, and the total comes out short. The proof page shows by how much.

Try the what-if switch

Why it’s hard to catch

Two increments rarely collide, so the program usually prints 2. That’s why the race detector matters: it reports the unsynchronized access itself, not a wrong result.

Rule of thumb

If two goroutines touch the same variable and one writes, synchronize. Run your tests with -race in CI.

Common questions

Is count++ atomic in Go?

No. count++ reads, adds and writes. Two goroutines can read the same old value and both write the same new one, so one increment is lost.

How do I find a data race in Go?

Run with the race detector: go run -race, go test -race. It prints WARNING: DATA RACE with both goroutines' stacks and exits with code 66, even on runs where the result happened to come out right.

Should I use sync.Mutex or atomic for a counter?

For one counter, atomic.Int64 is simplest: count.Add(1) is one indivisible step. Use a sync.Mutex when several values must change together.

Verified: this timeline matches a real program, checked in CI on Go 1.26 and 1.27. How we verify. Unclear or wrong? Tell us