← Back to CS Fundamentals

Race Condition

CS FundamentalsMidconcurrency

The Question

What is a race condition and how do you prevent it?

What a Strong Answer Covers

  • concurrent access to shared data
  • "outcome depends on execution order
  • concrete example
  • at least one fix

Senior-Level Answer

A race condition occurs when two or more threads or processes access shared data concurrently, and at least one of them modifies it, such that the final outcome depends on the non-deterministic order of execution. The result is unpredictable behavior — the program may produce correct results on some runs and incorrect results on others, making race conditions notoriously difficult to reproduce and debug.

A classic example is a bank account balance update. Suppose two threads both read a balance of $100. Thread A adds $50 and writes $150. Thread B adds $30 and writes $130. The final balance should be $180, but because both threads read the original value before either wrote, one update is lost. This is called a lost update and it is one of the most common manifestations of a race condition.

At a lower level, even a simple increment operation like counter++ is not atomic in most languages. It compiles to three separate instructions: load the value from memory, increment it, and store it back. If two threads execute these instructions interleaved, the counter may only increment once instead of twice.

There are several strategies to prevent race conditions. The most common is mutual exclusion using a mutex or lock. Before accessing shared data, a thread acquires the lock. Any other thread that tries to acquire the same lock blocks until the first thread releases it. This serializes access to the critical section.

Atomic operations are another approach. Hardware provides compare-and-swap (CAS) and other atomic instructions that complete in a single uninterruptible step. Languages expose these through constructs like Java's AtomicInteger or C++'s std::atomic. Atomics avoid the overhead of locks but only work for simple operations on single variables.

A third strategy is to avoid shared mutable state entirely. Immutable data structures, message passing between threads (as in Go's channels or Erlang's actor model), and thread-local storage all eliminate the conditions that allow races to occur. This is often the most robust approach because it removes the problem at the design level rather than patching it with synchronization.

What Separates a 2/3 from a 3/3

2/3 — Passing but Incomplete

Candidate defines a race condition as concurrent access producing unpredictable results and gives at least one example, but prevention strategies are vague.

3/3 — Strong Answer

Candidate clearly explains that the outcome depends on execution order, gives a concrete example (like lost update or counter increment), and names multiple prevention strategies (mutexes, atomics, avoiding shared mutable state) with enough detail to show real understanding.

Common Mistakes

  • Confusing race conditions with deadlocks — they are different concurrency problems
  • Thinking race conditions only happen with threads — they can occur with processes sharing memory or distributed systems
  • Assuming that using a language with a GIL eliminates all race conditions
  • Believing that making a variable volatile is sufficient to prevent races
  • Giving only 'use a lock' as prevention without considering lock-free or shared-nothing alternatives

Follow-Up Questions

  • What is the difference between a race condition and a data race? — A data race is specifically two unsynchronized accesses to the same memory where at least one is a write. A race condition is a broader logic bug.
  • How would you detect a race condition in production code? — Thread sanitizers (TSan), stress testing, logging with timestamps, and code review focused on shared mutable state.
  • What is a TOCTOU race and where does it commonly appear? — Time-of-check to time-of-use: checking a condition then acting on it non-atomically. Common in file system operations.
  • How do compare-and-swap operations work at the hardware level? — CAS atomically compares a memory location to an expected value and swaps it with a new value if they match.
  • Can you have a race condition in single-threaded code? — Yes — with asynchronous callbacks, signal handlers, or event-driven architectures.

Related Questions

  • Process vs Thread
  • Deadlock — 4 Conditions, Prevention
  • Thread Safety — Is dict Thread Safe?
  • HashMap Internals
  • Hash Collision — Chaining vs Open Addressing

Can You Explain This Cold?

Reading the answer is step one. Explaining it unprompted — under interview pressure — is what actually matters. Get AI-graded feedback on your answer with follow-up probes on your weak points.

Get Graded — Free Assessment
GrindQuestionsAITechnical interview assessment
TermsPrivacyAbout