ReviseAlgo Logo

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.

Last Updated: June 12, 2026 9 min read

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 += 1 is NOT atomic (read → increment → write)
  • Even x = x + 1 can 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