The four Coffman conditions
State them as a set, with a resource example for each, rather than as four disconnected definitions.
- Mutual exclusion. At least one resource can be held by only one thread at a time. A lock is exactly this.
- Hold and wait. A thread holds one resource while waiting for another.
- No preemption. A resource cannot be taken away from its holder. Only the holder releases it.
- Circular wait. There is a cycle: T1 waits for something T2 holds, T2 waits for something T1 holds, or a longer loop of the same shape.
The sentence the panel listens for comes next. These are necessary conditions, so if your design makes any one impossible, deadlock is impossible.
A program that deadlocks
The dining philosophers problem is the standard viva example, and it is easy to write. Five philosophers sit round a table with one fork between each pair. Each needs both neighbouring forks to eat.
import threading
N = 5
forks = [threading.Lock() for _ in range(N)]
def philosopher(i, meals=10_000):
left, right = i, (i + 1) % N
for _ in range(meals):
with forks[left]:
with forks[right]:
pass
threads = [threading.Thread(target=philosopher, args=(i,)) for i in range(N)]
for t in threads:
t.start()
for t in threads:
t.join()
print("everyone ate")
Run it enough times and it can hang. If every philosopher takes their left fork at the same moment, all four conditions hold. Each fork is a lock (mutual exclusion). Each philosopher holds one fork and waits for another (hold and wait). Nobody can grab a fork from a neighbour (no preemption). And philosopher 0 waits for 1, 1 for 2, and so on until 4 waits for 0 (circular wait).
Breaking circular wait with a lock order
Number the forks and make every philosopher pick up the lower-numbered fork first.
def philosopher(i, meals=10_000):
left, right = i, (i + 1) % N
first, second = min(left, right), max(left, right)
for _ in range(meals):
with forks[first]:
with forks[second]:
pass
Philosophers 0 to 3 behave as before, taking fork i and then fork i+1. Philosopher 4 is the one that changes. Their forks are 4 and 0, so they now take fork 0 first. A cycle would need someone holding a higher-numbered fork while waiting for a lower one, and the order makes that impossible. In the worst case philosophers 0 and 4 both want fork 0 first. Whichever loses waits while holding nothing, so the loop can never close and someone always gets to eat.
Say which condition you broke. Mutual exclusion, hold and wait and no preemption all still hold. Only circular wait is gone, and that is enough.
Breaking the other three, and what each costs
Panels often ask "could you break a different one?". Each option is real and each has a price.
| Condition broken | How | Cost |
|---|
| Mutual exclusion | Make the resource shareable, such as read-only data, or let one owner thread do all the writes | Impossible for most locks, since a counter or a fork genuinely cannot be shared |
| Hold and wait | Request every resource at once, or release everything before asking for more | Resources sit idle while held early, and a thread needing many can starve |
| No preemption | Try the second lock with a timeout and release the first on failure | Threads can retry in step forever without progress, which is livelock, so add random backoff |
| Circular wait | One global acquisition order | Every piece of code must follow the same order, including code written later |
Lock ordering usually wins because it costs nothing at run time. Its weakness is discipline: one function that takes locks in a different order brings the cycle back. The two-locks design page covers ordering on real objects, ties and calling unknown code under a lock.
Prevention, avoidance and detection
The next push is often "what is the Banker's algorithm?". It belongs to avoidance, not prevention. The system allows the four conditions to hold, but makes a process wait rather than grant any request that would move the system into an unsafe state. It needs each process's maximum demand in advance, which general-purpose programs rarely declare.
Detection lets deadlocks happen, finds the cycle in a wait-for graph and recovers by aborting a victim. Databases work this way, which is why application code must retry a transaction aborted for deadlock. The database deadlock page shows it in practice.
Why a cycle is not always a deadlock
A fresher answer often ends with "a cycle in the resource-allocation graph means deadlock". That is true only when every resource type has a single instance. If a resource type has two instances, say two printers, a cycle can exist while a third process holding a printer finishes and releases it, breaking the cycle. With multiple instances, a cycle is necessary but not sufficient. Getting this distinction right is what separates a recited answer from an understood one.
Name all four conditions, then break exactly one and say which. Lock ordering breaks circular wait at no run-time cost, provided every thread obeys the same order.
© 2026 Preptima. Originally published at preptima.com (opens in a new tab).