Mock 11 — Concurrency Heavy
Interview type: Concurrency / parallelism coding round Target role: Backend, systems, infrastructure, embedded, gaming, OS-adjacent Time limit: 60 minutes Format: ONE problem requiring real concurrency primitives (locks, condvars, channels, atomics) Hints policy: A hint on the primitive choice is acceptable; a hint on the race condition is borderline. Primary goal: Write code that is provably correct under concurrent access.
What This Mock Tests
Concurrency code is notoriously hard. The bar:
- Identify what needs synchronization (shared mutable state)
- Choose the right primitive (mutex vs RWLock vs channel vs atomic)
- Avoid deadlocks, livelocks, starvation
- Write a test that would catch the bug if present (not just one that passes by luck)
Scoring weights: Correctness (#5), Code Quality (#7), Language/Runtime (#12) are critical.
Pick One Problem
Problem A — Bounded Blocking Queue (with timeout)
q = BoundedQueue(capacity=10)
q.put(item) # blocks if full
q.put(item, timeout=5.0) # returns False on timeout
item = q.get() # blocks if empty
item = q.get(timeout=5.0) # returns None on timeout
q.close() # subsequent put → exception; get drains then returns None
Multiple producers, multiple consumers. Must be FIFO. Must support graceful close.
Problem B — Thread Pool (with shutdown semantics)
pool = ThreadPool(workers=4)
future = pool.submit(fn, arg)
result = future.result(timeout=5.0) # blocks until done
pool.shutdown(wait=True) # waits for all queued + running
pool.shutdown(wait=False) # rejects new, returns immediately
Required: bounded work queue, graceful drain, future-based result delivery, exception propagation.
Problem C — Read-Write Lock (Writer-Preferred)
lock = RWLock()
with lock.read():
# multiple readers concurrently
...
with lock.write():
# exclusive; blocks new readers waiting for writer
...
Required: many readers OR one writer; writers must not starve.
Problem D — Dining Philosophers (Deadlock-Free)
5 philosophers, 5 chopsticks. Each alternates think/eat. Implement so no deadlock, no philosopher starves.
Expected Communication Style
- Restate with the explicit concurrency requirements (“multiple producers, multiple consumers, FIFO, graceful close”).
- Identify shared mutable state. This is the most important step.
- Identify the invariant. (“Queue size never exceeds capacity”; “no two writers active simultaneously”; “no two adjacent philosophers eat simultaneously”.)
- Choose primitives with rationale. (“I need to block on full/empty — that means condvar, not just a lock.”)
- Code the critical section minimally. Hold the lock only across the shared-state mutation; release before any potentially-blocking call.
- Write a concurrency test. Not just “does it work once” — stress with many threads, verify the invariant.
- Discuss failure modes: what happens if a producer dies holding the lock? If close is called during a put?
Solution Sketches
A. Bounded Queue:
class BoundedQueue:
def __init__(self, capacity):
self._capacity = capacity
self._q = deque()
self._lock = threading.Lock()
self._not_full = threading.Condition(self._lock)
self._not_empty = threading.Condition(self._lock)
self._closed = False
def put(self, item, timeout=None):
with self._lock:
if self._closed: raise QueueClosed()
end = time.monotonic() + timeout if timeout else None
while len(self._q) >= self._capacity:
if self._closed: raise QueueClosed()
remaining = end - time.monotonic() if end else None
if remaining is not None and remaining <= 0: return False
self._not_full.wait(timeout=remaining)
self._q.append(item)
self._not_empty.notify()
return True
def get(self, timeout=None):
with self._lock:
end = time.monotonic() + timeout if timeout else None
while not self._q:
if self._closed: return None
remaining = end - time.monotonic() if end else None
if remaining is not None and remaining <= 0: return None
self._not_empty.wait(timeout=remaining)
item = self._q.popleft()
self._not_full.notify()
return item
def close(self):
with self._lock:
self._closed = True
self._not_full.notify_all()
self._not_empty.notify_all()
B. Thread Pool: N worker threads pulling from a shared bounded queue; each task wraps (fn, args, future). Workers set future’s result/exception. Shutdown sentinels (None) wake workers; wait=True joins all worker threads.
C. RWLock writer-preferred: Track reader_count, writer_active, waiting_writers. Reader acquires only if no writer active AND no waiting writers. Writer blocks while readers active; once it’s the chosen waiter, blocks all new readers.
D. Philosophers: asymmetric strategy — even-numbered grabs left then right, odd-numbered grabs right then left. Breaks the symmetry that causes circular wait → no deadlock. Alternative: use a hierarchical lock ordering (always grab lower-id chopstick first).
Common Failure Modes
- Used
ifinstead ofwhilearoundwait(). Spurious wakeups cause invariant violations. - Released the lock before notifying, or notified before updating state. Wakes a consumer that finds the queue empty.
- Held the lock during a callback / blocking I/O. Other threads stall.
- Used
notify()instead ofnotify_all()for close. Only wakes one waiter; others hang forever. - RWLock without writer preference → writer starvation.
- Philosophers: all grab left → deadlock. Classic.
- Test that runs once and passes. Not a concurrency test. Need stress (1000+ iterations across many threads).
- Used
concurrent.futures.ThreadPoolExecutorfor Problem B without implementing the underlying logic. Some interviewers accept this; many want you to build it.
Passing Bar
- Total score: 56/70 (average 4.0)
- Code correctness verified under stress (≥ 10K operations across ≥ 8 threads)
- Invariant explicitly stated and tested
- No use of high-level concurrency abstractions that hide the primitive (e.g.,
queue.Queuefor problem A) - Failure modes discussed
Follow-up Questions
For A:
- Lock-free version. → Discuss MPMC ring buffer with CAS; show awareness of ABA problem.
- Multi-priority. → Multiple internal queues, one per priority.
- Persist across restart. → Append-only log; replay on startup.
For B:
- Work-stealing pool. → Per-worker deque; idle workers steal from others.
- Dynamic resizing. → Grow workers under load; shrink when idle.
- Cancellable tasks. → Future.cancel() signals; worker checks flag.
For C:
- Fair RWLock (FIFO). → Single waiting queue; reader batching possible but trickier.
- Reader-preferred. → Easier to implement; writer starvation risk.
- Async version with futures. → Same logic; futures replace condvar.
For D:
- Variant where philosophers think for variable time. → Same algorithm.
- Generalize to N philosophers and K chopsticks. → Open problem; resource allocation graphs.
- Distributed version (philosophers on different machines). → Requires distributed deadlock detection.
Required Tests
- Single-thread correctness
- Multi-thread stress: many producers + consumers; assert no item lost, no item duplicated, no exception
- Timeout correctness:
put(timeout=0.1)returns False after 100ms when full - Close semantics: close during active put/get unblocks all waiters cleanly
- Invariant assertion: assert size ≤ capacity throughout, etc.
Example stress test for A:
def test_stress():
q = BoundedQueue(10)
produced = []
consumed = []
def producer(start, count):
for i in range(start, start + count):
q.put(i)
produced.append(i)
def consumer():
while True:
x = q.get(timeout=1.0)
if x is None: break
consumed.append(x)
threads = [threading.Thread(target=producer, args=(i*1000, 1000)) for i in range(10)]
consumers = [threading.Thread(target=consumer) for _ in range(5)]
for t in threads + consumers: t.start()
for t in threads: t.join()
q.close()
for t in consumers: t.join()
assert sorted(consumed) == sorted(produced)
assert len(consumed) == 10_000
Required Discussion
- The invariant your code maintains
- The lock ordering (if multiple locks)
- The worst-case latency (lock contention)
- What happens under crash mid-operation
- How you’d debug a deadlock if one were reported (jstack, py-spy dump, gdb)
Self-Evaluation Template
Mock 11 — Concurrency
Date: _______
Problem: _______
Time: ___ / 60 min
Scores (1–5):
___ Total /70
Stress test passed (≥ 10K ops)? Y/N
Invariant explicitly stated? Y/N
Failure modes discussed? Y/N
Any race condition found post-hoc? (List:)
Action item:
What to Do If You Fail
- Race condition in submitted code: This is the #1 reason to repeat — concurrency bugs in production are catastrophic.
- Couldn’t choose the right primitive: Read your language’s concurrency chapter (Phase 9). Understand condvar vs channel vs atomic before next attempt.
- Stress test exposed a bug you didn’t anticipate: Lab 05 (stress harness) applied to concurrent code is your training.
- Pass twice consecutively before Mock 12.