Race condition and deadlock interview questions

8 operating systems interview questions on spotting races and deadlocks, each with the correct answer and a full explanation.

transfer(a, b) locks a then b. Two threads run transfer(x, y) and transfer(y, x) at the same time and hang. What is the standard fix?
Show answer

Answer

Always lock the two accounts in a fixed order, such as by account id
Each thread holds one lock and waits for the other: circular wait. A global lock order breaks the cycle. Timeouts turn a deadlock into failures or livelock and still need retry logic. Atomic balances make each update safe but not the pair of updates. Read locks do not allow writes and both threads would still need the write lock.

Source: OSTEP: Common Concurrency Problems

A large codebase with dozens of locks deadlocks a few times a week, always with two threads each holding one lock and waiting for the other. Which prevention strategy is the practical one?
Show answer

Answer

Circular wait, by acquiring locks in a global order
Deadlock needs all four Coffman conditions, so removing any one prevents it. A global lock order removes circular wait and is cheap and local to each call site. Removing mutual exclusion means rewriting to lock free code, which is hard and error prone. Preempting locks would leave data half updated. A single thread removes the concurrency the system needs, which is not a fix.

Source: OSTEP: Common Concurrency Problems

A cache lookup takes a mutex, and on a miss fetches from a remote service (100 ms) before releasing it. What is the main problem?
Show answer

Answer

All lookups, including hits for other keys, queue behind the slow fetch
Holding a lock across slow I/O serialises every caller for the duration, so throughput collapses to about 10 misses per second and even hits wait behind them. Fetch outside the lock with a per key in flight marker or future. I/O under a lock is legal, just slow. Preventing duplicate fetches does not require blocking every other key. Releasing a lock does not discard data.

Source: OSTEP: Common Concurrency Problems, SpaceComplexity: Databricks onsite interview

Two threads call get_instance() at the same time with this code. What can go wrong?
if instance is None:
    instance = Expensive()
return instance
Show answer

Answer

Both can see None and construct two instances
It is check then act: both threads pass the check before either assigns. Use a lock (with a re-check inside), or a language once primitive. Atomic assignment only means the pointer is not torn; it does not stop two constructions. There is no lock here, so no deadlock and no waiting.

Source: OSTEP: Concurrency: An Introduction, Java Concurrency in Practice

Double checked locking: check instance without the lock, lock, check again, construct and assign. In Java or C++, what extra requirement makes it correct?
Show answer

Answer

The instance field needs acquire and release semantics (volatile in Java, atomic in C++)
Without ordering, the compiler or CPU can publish the pointer before the constructor's writes are visible, so the unlocked first check can return a half built object. Making the field volatile or atomic with release on store and acquire on load fixes it. The inner check prevents double construction, not unsafe publication. Synchronizing the constructor does not order the publish. Locking the outer check works but is just ordinary locking.

Source: Java Concurrency in Practice, What Every Programmer Should Know About Memory

Two threads each lock one resource, try lock the other, and on failure release and retry immediately. Both stay busy at 100% CPU but never finish. What is this and the usual fix?
Show answer

Answer

Livelock; add randomized backoff before retrying, or use a global lock order
The threads are not blocked, they keep reacting to each other in lockstep, which is livelock. Random backoff breaks the symmetry, and lock ordering avoids the conflict entirely. It is not deadlock because nobody is waiting; they already use try lock. Starvation means one thread progresses while another never does. Atomic resources do not resolve the conflict over two locks.

Source: OSTEP: Common Concurrency Problems

A high priority task waits on a lock held by a low priority task, while a medium priority CPU bound task keeps preempting the low one. What is the standard fix?
Show answer

Answer

Priority inheritance: the lock holder temporarily runs at the waiter's priority
This is priority inversion, the Mars Pathfinder bug. Priority inheritance boosts the low priority holder above the medium task so it finishes and releases the lock. Raising the medium task makes it worse. Spinning burns the CPU the holder needs. A longer slice does not stop priority preemption.

Source: What really happened on Mars? (Glenn Reeves account)

A waiter checks a flag under a lock, releases the lock, and then calls wait on a condition variable. The setter takes the lock, sets the flag and signals. Sometimes the waiter sleeps forever. Why?
Show answer

Answer

The signal can arrive between unlocking and waiting, and it is lost because condition variables have no memory
Condition variable wait must atomically release the mutex and sleep, which is why it takes the mutex as an argument. Releasing it first opens a window where the signal fires with no one waiting and is dropped. A timeout masks the bug with latency. Signalling before setting makes it worse. Sharing a condition variable is its whole purpose.

Source: OSTEP: Condition Variables, pthread_cond_wait, The Open Group Base Specifications

These questions come from the Operating Systems Interview Practice pack in Bertopia.

More operating systems topics

Practise these on your commute