Concurrency FundamentalsRace Conditions & Critical SectionsMedium⏱️ ~3 min

Data Races vs Race Conditions

Key Insight
Data races and race conditions are related but distinct. You can have one without the other. Understanding the difference matters for both debugging and choosing tools.
Data Race vs Race Condition
DATA RACEUnsynchronized accessto shared memoryNo lock, no atomic, no barrierDetectable by tools (TSan)Undefined behavior in C/C++RACE CONDITIONTiming-dependentincorrect behaviorCan occur WITH proper lockingHard to detect automaticallyLogic bug, not memory bug

Data Race Without Race Condition

Two threads write the same value to a variable. The final state is correct regardless of order, but it is still a data race (undefined behavior in C/C++).

Race Condition Without Data Race

Two threads use proper locking but the logic is still timing-dependent. Example: Thread A locks, checks empty, unlocks. Thread B locks, adds item, unlocks. Thread A proceeds as if empty. All accesses synchronized, still wrong.

Why It Matters

Tools detect data races: ThreadSanitizer, Helgrind can find data races automatically. They cannot find race conditions.

Fixing data races is not enough: Your code can be data-race-free and still have race conditions. You need to think about correctness, not just synchronization.

Summary: Data race = memory access problem (tool-detectable). Race condition = logic problem (requires human reasoning).
💡 Key Takeaways
Data race: unsynchronized concurrent memory access where at least one is a write. Undefined behavior in C/C++.
Race condition: program correctness depends on timing. Can occur even with proper synchronization.
Data races are detectable by tools like ThreadSanitizer, Helgrind, or Go race detector. Run tests with race detection enabled.
Race conditions require semantic understanding to detect. They represent logic errors, not memory safety violations.
Eliminating data races does not eliminate race conditions. You can have correct synchronization but wrong logic.
📌 Examples
1Data race free but buggy: lock(); if (queue.empty()) { unlock(); return; } item = queue.pop(); unlock(); Another thread could pop between empty check and pop if the lock is released.
2Data race: int x; thread1: x = 1; thread2: print(x). Without synchronization, thread2 might see 0, 1, or garbage.
← Back to Race Conditions & Critical Sections Overview