Blocking queue and thread pool interview questions
11 operating systems interview questions on producer consumer, blocking queues, thread pools and backpressure, each with the correct answer and a full explanation.
Producer consumer and blocking queues
A consumer waits like this on a shared queue with several consumers. What bug can it cause?
lock.acquire()
if queue.empty():
not_empty.wait()
item = queue.pop()
lock.release()
Show answer
Answer
After waking, the queue can be empty again, so pop fails; the check must be a while loop
Wait releases the lock and reacquires it on return, but between the notify and the reacquire another consumer can take the item, and spurious wakeups are allowed by POSIX. Only a while loop re-checks the predicate. Reacquiring the lock is true but does not make the state still valid. Wait does release the lock, so it does not deadlock. Lost notifications are prevented here because the predicate is checked under the lock before waiting.
A bounded queue uses one condition variable for both producers and consumers, every wait is inside a while loop that re-checks its condition, and it calls notify (wake one) after each put and get. What can happen?
Show answer
Answer
A producer can wake another producer instead of a consumer, and everyone ends up waiting
With one shared condition, notify may wake a thread of the same kind, which re-checks, goes back to sleep, and the wakeup meant for the other side is lost; all threads can end up asleep. Use two conditions (not_full and not_empty) or notify_all. While loops prevent wrong progress but not this stall. Ordering is decided by the queue, not the notifications. Capacity is protected by the while check, so overflow does not happen.
In a correct bounded queue, which state change must happen while holding the mutex associated with the condition variable?
Show answer
Answer
Changing the queue contents that the waiters' predicate reads
The predicate state (queue length) must be updated under the same mutex the waiter holds while checking it; otherwise a waiter can check, miss the update, and sleep forever. POSIX allows signalling with or without the mutex held, so notify outside the lock is not itself the bug (some libraries like Python require holding it, but that is an API rule). A condition variable is only a wait queue and protects nothing. Updates outside the lock reintroduce the lost wakeup race.
A semaphore based bounded buffer has empty (starts at N), full (starts at 0) and a mutex. Which put ordering is correct?
Show answer
Answer
Wait on empty, lock mutex, insert, unlock mutex, signal full
The counting semaphore must be waited on before the mutex: a producer that blocks on empty while holding the mutex stops every consumer from freeing a slot, so all threads deadlock. Waiting on full in put inverts the roles of the two semaphores. Inserting before waiting on empty can overflow the buffer because the slot was never reserved.
You add put(item, timeout) to a blocking queue. The wait can wake spuriously or lose the race for a free slot. How should the timeout be handled?
Show answer
Answer
Compute a deadline once, and on each loop iteration wait only for the time remaining until it
A deadline keeps the total wait bounded across repeated wakeups and still re-checks the predicate each time. Reusing the full timeout in a loop restarts the clock on every spurious or stolen wakeup, so the call can block far longer than requested. A single if wait brings back the stolen slot bug. Sleeping outside the lock wastes the full timeout even when a slot frees immediately, and the single check is still racy.
Consumers block in get() on an empty queue. You add close() so they exit cleanly. What must close do?
Show answer
Answer
Set a closed flag under the lock and notify all waiters, and get returns or raises when closed and empty
Waiters check the closed flag in their while predicate, so it must be set under the lock and every waiter must be woken with notify all; otherwise some stay asleep forever. An unlocked write can race with a waiter that checked the flag and is about to sleep, losing the wakeup. Chaining one notify relies on every consumer remembering to pass it on and breaks if any exits by another path. Killing threads skips cleanup and leaves locks and items in unknown states.
A lock free ring buffer with one head and one tail index, each updated with plain atomic loads and stores, is correct for which setup?
Show answer
Answer
Exactly one producer and one consumer
With one producer only it writes tail and with one consumer only it writes head, so each index has a single writer and loads and stores with release and acquire ordering are enough. With multiple producers two can read the same tail and write the same slot; with multiple consumers two can take the same item. Supporting those needs CAS on the indices or per slot sequence numbers.
A thread pool with 16 workers and an unbounded task queue gets a burst it cannot keep up with for 10 minutes. What happens?
Show answer
Answer
The queue grows until memory runs out, and latency for queued tasks grows without limit
With an unbounded queue nothing pushes back, so arrivals above throughput pile up in memory and every new task waits behind the backlog. A bounded queue with a rejection policy, caller runs, or load shedding is backpressure. Many pools (Java ThreadPoolExecutor) only grow past core size when the queue is full, which never happens with an unbounded one. Rejection needs a bounded queue. Queuing adds latency, not throughput.
A pool runs tasks that spend 90% of their time waiting on network calls and 10% on CPU, on 8 cores. Roughly how many threads keep the CPUs busy?
Show answer
Answer
About 80
Threads needed is about cores times (1 + wait time / compute time) = 8 times (1 + 9) = 80. Eight threads would leave cores idle 90% of the time. Nine adds just one. Eight thousand wastes memory and adds switching while the CPUs are already saturated near 80.
Tasks in a fixed pool of 4 threads submit a subtask to the same pool and block waiting for its result. Under load the service freezes. Why?
Show answer
Answer
All 4 threads are blocked waiting for subtasks that sit in the queue with no free thread to run them
This is thread starvation deadlock: the parents occupy every worker, and the children they wait on cannot start. Use a separate pool for subtasks, avoid blocking (compose futures), or use a work stealing pool designed for fork and join. Executor queues are thread safe. A standard submit does not run the task on the caller. Pending futures are referenced by the queue.
An ingest service reads from a client stream and hands records to a worker pool. Records must not be dropped, and memory must stay bounded. Which policy fits?
Show answer
Answer
A bounded queue where the producer blocks when full, slowing reads from the client
Blocking the producer propagates backpressure upstream (TCP flow control slows the client), so nothing is lost and memory is capped. Dropping the oldest or the newest violates the no loss rule. An unbounded queue loses nothing until it exhausts memory, then loses everything.