Knowledge Guide
HomeConcurrencyConcurrency Foundations

Race Conditions — A Traced Interleaving of counter++

The bug that prints 1 when you expected 2

This is the single most foundational concurrency bug, and the one interviewers probe first. Two threads each increment a shared counter once. The "obvious" answer is 2. The real answer is sometimes 1 — and which one you get depends on timing you don't control. Understand this one trace and every other race-condition bug becomes recognizable.

Why counter++ is three operations, not one

The source looks atomic, but the CPU cannot add to a memory location in one indivisible step. The compiler turns count++ into a read-modify-write sequence:

  1. LOAD — copy counter from memory into a CPU register
  2. ADD — increment the register
  3. STORE — write the register back to memory

A thread can be preempted between any two of these steps. The window between LOAD and STORE is the critical section: if a second thread reads counter while the first is mid-update, both compute their new value from the same stale read, and one write clobbers the other. That clobbered write is a lost update.

Two threads interleaving counter++: both read 0, both store 1, final value 1 instead of 2
Two threads interleaving counter++: both read 0, both store 1, final value 1 instead of 2

The interleaving, step by step

Here is one specific bad ordering (there are many). Follow the register values and the memory cell:

#ThreadOperationr1r2counter (memory)
1T1LOAD r1 ← counter00
2T2LOAD r2 ← counter000
3T1ADD r1 = 0+1100
4T2ADD r2 = 0+1110
5T1STORE counter ← r1111
6T2STORE counter ← r2111

Both threads "did their job," yet the counter advanced by one. T2's store at step 6 overwrote T1's — it never saw T1's increment because it had already read the old value at step 2.

Reproduce it yourself

This Java prints a different number below 200,000 on almost every run — the signature of a race:

class Counter {
    private int count = 0;
    void increment() { count++; }      // LOAD, ADD, STORE — NOT atomic
    int get() { return count; }
}

Counter c = new Counter();
Runnable job = () -> { for (int i = 0; i < 100_000; i++) c.increment(); };
Thread t1 = new Thread(job), t2 = new Thread(job);
t1.start(); t2.start();
t1.join();  t2.join();
System.out.println(c.get());   // expected 200000 — prints e.g. 143_271, varies every run
With a lock, thread 1 completes its full read-modify-write before thread 2 starts, so the final value is 2
With a lock, thread 1 completes its full read-modify-write before thread 2 starts, so the final value is 2

The fix: make the read-modify-write indivisible

You must guarantee that no thread observes the counter mid-update. Three standard ways, cheapest last:

// 1. Lock — mutual exclusion around the whole RMW
synchronized void increment() { count++; }

// 2. Explicit lock (same idea, more control)
lock.lock(); try { count++; } finally { lock.unlock(); }

// 3. Atomic — a single hardware compare-and-swap, no lock needed (fastest for a counter)
private final AtomicInteger count = new AtomicInteger();
void increment() { count.incrementAndGet(); }   // now ALWAYS prints 200000

All three force the LOAD→ADD→STORE to happen as one indivisible step, so the interleaving in the first diagram becomes impossible — the second thread either runs entirely before or entirely after.

Pitfalls that bite real engineers

Takeaways


Re-authored from scratch for this guide; both interleaving diagrams hand-authored as SVG. The counter++ example follows the canonical treatment in Java Concurrency in Practice (Goetz et al., ch. 2). See also: Synchronization Constructs, Mutex Lock, and the Memory Model & Visibility lesson.

🤖 Don't fully get this? Learn it with Claude

Stuck on Race Conditions — A Traced Interleaving of counter++? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Race Conditions — A Traced Interleaving of counter++** (Concurrency) and want to truly understand it. Explain Race Conditions — A Traced Interleaving of counter++ from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Race Conditions — A Traced Interleaving of counter++** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Race Conditions — A Traced Interleaving of counter++** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Race Conditions — A Traced Interleaving of counter++** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes