Concurrency
Deadlocks and Race Conditions
Common concurrency bugs — understanding deadlocks, livelocks, race conditions, and strategies to detect and prevent them.
Interview: Debugging concurrency — identifying and fixing these bugs shows deep understanding of thread safety.
Concurrency bugs are among the hardest to find and fix because they're non-deterministic — they depend on thread scheduling timing. The two most common types are deadlocks (threads wait forever for each other) and race conditions (shared state corrupted by concurrent access).
Deadlocks
- Two or more threads each hold a lock and wait for the other's lock
- Coffman conditions (all four must hold): mutual exclusion, hold-and-wait, no preemption, circular wait
- Deadlocks are silent — the program hangs with no error message
- Use
lock.acquire(timeout=5)to detect deadlocks (returns False on timeout)
Race Conditions
- Multiple threads read-modify-write shared data without synchronization
- The result depends on thread execution order — different each run
- Classic example:
count += 1is NOT atomic (read → increment → write) - Even
x = x + 1can lose updates under contention
Prevention Strategies
- Lock ordering: Always acquire locks in a consistent global order (e.g., by id())
- Timeouts: Use
lock.acquire(timeout=N)to detect and recover from deadlocks - Minimize shared state: Use thread-local storage, queues, or immutable data
- Use higher-level primitives: Queue, ThreadPoolExecutor instead of raw locks
- RLock for recursion: When a thread might need to acquire the same lock multiple times
Livelocks
- Threads keep retrying but never make progress (unlike deadlock, they're not blocked)
- Example: two threads both detect a conflict, back off, retry, conflict again — forever
- Fix: add random backoff (jitter) to break the symmetry
Interview Insight
Deadlocks require all four Coffman conditions — break any one to prevent them. Lock ordering is the simplest prevention: sort locks by id() and always acquire in that order. For race conditions: identify all shared mutable state and protect with locks or use thread-safe alternatives.
Common Pitfall
Assuming "simple" operations are atomic. list.append() and dict[key] = value are atomic under the GIL, but x += 1, dict.update(), and list.sort() are NOT. When in doubt, use a lock.
Use Cases
Bank account transfers — preventing deadlocks with lock ordering
Thread-safe counters — protecting shared accumulators
Connection pools — managing shared resource access
Producer-consumer — using queues to avoid shared mutable state
Parallel algorithms — coordinating phases without deadlocks
Common Mistakes
Acquiring locks in different orders across threads — classic deadlock cause
Assuming += and similar operations are atomic — they are NOT (read-modify-write)
Not using timeout on lock.acquire() — program hangs silently on deadlock
Using global variables without locks — race conditions produce wrong results
Overusing locks when queues or thread-local storage would be simpler and safer