Skip to main content

Deadlocks & The 4 Necessary Coffman Conditions

Easy

Interview Question: "What is a deadlock? State the four necessary Coffman conditions, and explain the Resource Allocation Graph (RAG) rule regarding cycles."

A deadlock is what happens when two threads each hold something the other one needs, and neither will let go. Nobody moves, forever.


The analogy: the pencil and the ruler​

Alice and Bob are drawing geometry diagrams together. Drawing a line needs both a pencil and a ruler.

Alice
holds Pencil, wants Ruler
stuck waiting
Bob
holds Ruler, wants Pencil

Alice has the pencil and asks Bob for the ruler. Bob has the ruler and says "give me the pencil first." Neither one gives up what they're holding, and neither can finish without the other's tool. They're stuck — forever, unless something outside the two of them intervenes.


The four conditions that all have to be true (Coffman, 1971)​

A deadlock can only happen if all four of these hold at once. Break even one, and a deadlock becomes impossible.

ConditionWhat it meansIn the analogy
1. Mutual exclusionAt least one resource can only be used by one thread at a time.Alice and Bob can't both write with the same physical pencil at once.
2. Hold and waitA thread holds one resource while waiting for another.Alice holds the pencil while waiting for the ruler.
3. No preemptionResources can't be forcibly taken away — only released voluntarily.Nobody can snatch the ruler out of Bob's hand.
4. Circular waitThere's a closed loop of threads each waiting on the next.Alice waits on Bob, Bob waits on Alice.

The trap: does a cycle always mean deadlock?​

Interviewers like to draw a Resource Allocation Graph and ask whether a cycle in it always means deadlock. The answer depends on how many instances of each resource exist:

  • Single instance per resource type: yes — a cycle means deadlock, no exceptions.
  • Multiple instances per resource type: not necessarily. A cycle can exist without anyone actually being stuck, if some other process outside the cycle is holding an extra instance of that resource and is about to finish and release it — breaking the wait before it becomes permanent.

How systems actually prevent deadlocks: lock ordering​

The most common real-world fix targets circular wait specifically — if you can't form a cycle, you can't deadlock.

  1. Give every resource in the system a unique number (Lock A = 1, Lock B = 2, and so on).
  2. Rule: a thread can only request a new lock if its number is higher than every lock it already holds.

Why this works: if a circular wait existed, you'd need F(R0)<F(R1)<⋯<F(Rn)<F(R0)F(R_0) < F(R_1) < \dots < F(R_n) < F(R_0) — which says a number is less than itself. That's impossible. So strict, global lock ordering rules out circular wait completely.


Say this in the interview​

"A deadlock needs all four Coffman conditions at once: mutual exclusion, hold-and-wait, no preemption, and circular wait. In a resource allocation graph, a cycle only guarantees deadlock when resources have a single instance each — with multiple instances, a cycle can exist safely. In practice, the usual fix is enforcing a global lock ordering, which makes circular wait mathematically impossible."


Code: reproducing an inverted-order deadlock​

import threading
import time

lock_a = threading.Lock()
lock_b = threading.Lock()

def thread_1_task():
with lock_a:
print("[Thread 1] Acquired Lock A")
time.sleep(0.1) # Simulates work, giving Thread 2 time to acquire Lock B
print("[Thread 1] Waiting for Lock B...")
with lock_b:
print("[Thread 1] Acquired Lock B!")

def thread_2_task():
with lock_b: # Inverted lock ordering!
print("[Thread 2] Acquired Lock B")
time.sleep(0.1)
print("[Thread 2] Waiting for Lock A...")
with lock_a:
print("[Thread 2] Acquired Lock A!")

if __name__ == "__main__":
t1 = threading.Thread(target=thread_1_task)
t2 = threading.Thread(target=thread_2_task)

t1.start()
t2.start()

t1.join()
t2.join()
# Program deadlocks here! Both threads wait indefinitely.