Explain a race condition and how a mutex actually prevents it, with a concrete example.
A race condition happens when two threads read and write shared state without coordination, and the final result depends on the unpredictable order their operations happen to interleave in. The classic case is “read, modify, write”: both threads read the same starting value, both compute an update based on it, and both write back, so one thread’s update silently overwrites the other’s instead of both being applied.
How a mutex actually fixes it
A mutex (mutual exclusion lock) guarantees that only one thread can execute the critical section, the read-modify-write sequence, at a time. Any other thread trying to enter has to wait until the lock is released, which eliminates the interleaving that caused the lost update, not by making things faster, but by making the risky sequence atomic from every other thread’s point of view.
Best practice
Keep the locked critical section as small as possible, only the actual shared-state read-modify-write, since holding a lock longer than necessary blocks other threads and can turn a correctness fix into a performance bottleneck or, in the worst case, a deadlock if locks are acquired in inconsistent orders across different code paths.
Edge case interviewers probe for
What happens if two different locks are acquired in different orders by two different threads? That’s a classic deadlock setup, thread A holds lock 1 and waits for lock 2, while thread B holds lock 2 and waits for lock 1, and neither ever proceeds. The fix is establishing and following a consistent global lock-acquisition order everywhere in the codebase.
Common mistake
Assuming an operation is “probably atomic enough” without checking, like `counter += 1` in most languages, which is actually a read, an add, and a write as three separate steps, fully capable of losing updates under concurrency even though it looks like one indivisible line of code.
What the interviewer is checking
Whether you understand a mutex as enforcing atomicity of a specific critical section, not as a general “make things safe” incantation, and whether you’re aware of the deadlock risk locks themselves introduce.
Imagine two people looking at the same notepad that says “5,” both deciding to add 1 to it, and both writing “6” back, instead of the correct final answer of “7.” Each person read the notepad before the other person’s update landed, so one person’s addition just got erased. That’s a race condition: the final result depends on the unlucky timing of who read and wrote when.
A mutex is like a single pen that only one person can hold at a time: whoever has the pen reads the notepad, updates it, and puts the pen back before the next person can even look. Now nobody can start their own read until the previous person’s entire read-update-write is finished, so the notepad correctly ends up at “7.”
Why interviewers ask this
Concurrency bugs are subtle, hard to reproduce, and common in real backend systems, this question checks whether you can reason precisely about interleaving rather than hand-waving “just add a lock.”
What a strong answer signals
That you can identify the exact critical section that needs protecting, and that you’re aware locks introduce their own risk (deadlock, contention) rather than being a free fix.
Common follow-ups
- What’s the difference between a mutex and a semaphore?
- How would you detect a deadlock in production?
- When would an atomic operation (like compare-and-swap) be preferable to a full mutex?
Advanced variation
“How would you fix this without locking at all?” expects discussing lock-free approaches like compare-and-swap atomics, which retry the update instead of blocking, avoiding lock contention and deadlock risk entirely for simple cases.
A rate limiter tracked request counts in a shared in-memory counter incremented by multiple worker threads. Under real concurrent load, the counter consistently undercounted requests, sometimes letting through more requests than the configured limit allowed, because concurrent `read, increment, write` sequences were losing updates. Wrapping the increment in a mutex made the read-modify-write atomic across threads, and the counter matched the actual request volume exactly, closing the rate-limit bypass that had gone unnoticed until a load test caught it.
# Racy: counter += 1 is read, add, write as separate steps
counter = 0
def increment():
global counter
counter += 1 # two threads can interleave here and lose an update
# Fixed: mutex makes the read-modify-write atomic across threads
import threading
lock = threading.Lock()
counter = 0
def increment_safe():
global counter
with lock:
counter += 1- 1A race condition comes from unsynchronized threads reading and writing shared state in an unpredictable order.
- 2Even simple-looking operations like `counter += 1` are multiple steps and can lose updates under concurrency.
- 3A mutex makes the critical section atomic by letting only one thread execute it at a time.
- 4Keep locked sections minimal, holding a lock too long turns a correctness fix into a bottleneck.
- 5Inconsistent lock-acquisition order across code paths is the classic cause of deadlock.