What it is
Concurrency problems ask you to build a small, correct piece of shared infrastructure — a bounded queue, a counter, a rate limiter, a reader-writer lock — that many threads use at once, using locks, condition variables, or semaphores as your only primitives. The core skill is distinguishing what must be atomic (a check-then-act sequence, an increment) from what can genuinely run in parallel (independent reads), and expressing "wait until this condition becomes true" without a thread ever burning CPU in a polling loop. Correctness here is usually provable in a few specific properties — no lost update, no double-wake, no starvation — that the interviewer will test by naming an interleaving, not by reading your code line by line.
When it applies
- Multiple threads read and write the same mutable state, and getting the interleaving wrong produces a lost update, a double-count, or a race rather than a wrong final answer on a single run.
- A caller must block until some condition holds (space in a queue, an item present, a turn arrives) and wake efficiently rather than re-checking in a loop.
- Two categories of access have different sharing rules (many readers can overlap, but a writer needs exclusivity), or ordering must hold across threads regardless of scheduling.
- If the "concurrency" is actually about coordinating across separate machines or processes (nodes agreeing on a value, requests hitting different servers), that's the distributed-systems territory covered by
systems-fundamentalsorhld, not this hub — locks and condition variables inside one process don't solve a cross-machine problem.
Tells in the problem
- "Multiple threads call this concurrently," or a guarantee stated as "no item is ever lost or delivered twice."
- A requirement that a blocked caller "sleep, not spin" or "must not busy-wait" — a direct signal to use a condition variable rather than a polling loop.
- Distinct categories of caller with different exclusivity needs (readers vs. writers, producers vs. consumers).
- A named hazard the design must specifically avoid: deadlock, starvation, or one caller's requests reordering another's.
- "Do not use
threading.Lock's higher-level cousinX; build it fromLock/Condition, and explain the choice" — the interviewer wants you to reconstruct the primitive, not call a library that already solves it.
The classic mistake
A check-then-act race hides in code that protects the read and the write of shared state with a lock separately, but leaves the decision in between un-protected — checking if queue is full under one lock acquisition and then appending under a second one, with another thread's put or get sliding in between the two and invalidating the check. It slips through because both halves look locked when read individually; the bug only shows up under real contention, never in a single-threaded test. Guard against it by identifying the smallest span of code where the check and the action must be seen as one atomic unit, and holding one lock (or one condition-variable wait) across that entire span, not just around each half.
Questions to start with
- Thread-Safe Counter — the smallest possible shared-state race, a clean way to confirm you can reason about a lost update before anything else.
- Print in Order with Threads — pure ordering across threads with no shared data at all, isolating the wait/wake mechanism itself.
- Bounded Blocking Queue — the classic producer-consumer shape, where both directions (full and empty) need their own wait condition.
- Read-Write Lock — two access categories with different sharing rules, plus an explicit starvation requirement to design against.
- Dining Philosophers Deadlock — asks you to both reproduce a deadlock and fix it, the sharpest test of naming which condition you broke.