Interview Prep Zoneby CuriouserLabs

Question 2 of 5 · senior level

What is a race condition? Why is counter++ broken under concurrency, and what are your options to fix it?

🎤 Say this first

A race condition is when correctness depends on the uncontrolled timing of threads. counter++ is a read-modify-write of three steps; two threads can both read the same value and one increment is lost. Fixes, in order of preference: don't share (confinement/immutability), use AtomicLong/LongAdder for counters, or guard the critical section with one lock — which buys both atomicity and visibility.

The full picture

🎬 Race condition on counter++ (and the fix)

1 / 8
Thread A 🐱Thread B 🐶counter = 0shared, unguardedcounter++ = read → add → write. Interleave the middle and updates vanish.

Two threads run counter++ concurrently. It looks atomic, but it compiles to three steps: read, add 1, write back.

Watch the lost update happen, then watch a monitor serialize the two threads.

The senior-level framing: a data race needs shared + mutable + unsynchronized state. Remove any one of the three and the race is gone. That ordering is also your design priority — the best concurrency bug is the one made impossible, not the one locked away.

FixWhenTrade-off
Immutability / confinementDefault choice — share nothing mutableMay require redesign; copies cost memory
AtomicInteger / LongAdderSingle-variable counters, sequencesCAS retries under heavy contention; LongAdder fixes that at the cost of read accuracy
synchronized / ReentrantLockMulti-variable invariants (check-then-act)Contention, possible deadlock, requires discipline
Concurrent collectionsShared maps/queuesCompound actions still need compute()-style atomic methods

🔄 Likely follow-up questions

  • What's the difference between a data race and a race condition? (a race condition can exist with no data race)
  • How does LongAdder beat AtomicLong under contention?
  • How would you detect a race in a running system?