home
›
cheatsheets
›
computer_science
›
concurrency_multithreading
Free to read Every card and diagram, and Quiz me
2
Free account Star cards. Your Quiz me marks are kept, so next time you drill just what you missed
3
Pro All 54 answers on this sheet, My sheet across every topic, and PDFs
A free account keeps your stars and Quiz me marks. Pro adds all 54 answers on this sheet, My sheet across every topic, and PDFs.
/
Read
Quiz me
Comfortable
Compact
My misses 0
Click any code to copy it
Done 0 /0
To review 0
On this sheet
The model
01 Races & shared state02 Locks & critical sections03 Deadlock & starvation04 Waiting & signaling05 Atomics & memory visibility06 CAS & lock-free code07 Thread pools & backpressure08 Async & the event loop09 Scaling under contention10 Finding concurrency bugs
The model Threads in one process share its memory. Most concurrency bugs start with shared mutable state , so find it first and decide what guards it. A race is a bug whose result depends on timing. count++ reads, adds and writes in three steps, so two threads can both read 5 and both write 6. A mutex makes threads take turns through a critical section . Keep that section short. Take several locks in one fixed order, or threads can deadlock . Without a lock, an atomic or a memory barrier, a thread may never see another thread's write. That's a visibility bug, and it differs from a race. Concurrency overlaps tasks that wait. Parallelism runs tasks on several cores at once. Async I/O helps work that waits. Only more cores speed up work that computes.Each arrow is one step, top to bottom. With no lock, A and B both read 5 and write 6, so one update is lost. With a mutex, B waits, then reads 6 and writes 7.
Races & shared state why threads go wrong, and what makes code safe Concurrency or parallelism Tell concurrency from parallelism Concurrency overlaps tasks in time, even on one core. Parallelism runs them at once on several cores.
Pick the one a job needs Concurrency for work that waits, parallelism for work that computes.
Trap Mixing them up adds cores to work that only waits, or threads to work that never waits.
Data race or race condition Define a data race Two threads access the same memory without synchronization, and at least one writes.
Have a race condition with no data race Lock every access but assume A arrives before B. The order can still be wrong.
Tip A mutex or atomic fixes a data race. A logic race needs a new protocol, not more locks.
Trap In C++, any data race is undefined behavior, even one whose output looks right.
Race conditions Define a race condition A bug where the result depends on thread timing.
Explain how an increment loses updates count++ It reads, adds and writes in three steps. Both threads read 5, and both write 6.
Spot a check-then-act race if (!map.has(k)) map.set(k, v) Both pass the check, then both write. In JavaScript, an await between them is enough.
Say why a race is hard to reproduce The bad timing is rare. Production load and GC pauses widen the window.
Trap Rerunning until it fails doesn't debug a race. Find the shared state and guard it.
Make code thread-safe Define thread-safe It works under any interleaving, with no extra locking by callers.
Make shared data safe Make it immutable, keep it in one thread, or guard it with a lock or atomic.
Give each thread its own copy Use a ThreadLocal. In a pool, call remove() in finally, or the next task inherits the value.
Trap Two thread-safe calls in a row can still race, like a check then an insert. One lock must cover both.
Locks & critical sections let one thread at a time touch shared state Mutexes and critical sections Stop a race with a mutex Lock before touching shared data, unlock after. Other threads wait their turn.
Define a critical section The code that touches shared state. A lock lets one thread run it at a time.
Release the lock even when the code throws lock.lock(); try { work(); } finally { lock.unlock(); }
Keep a critical section short Copy what you need, release the lock, then do the slow work.
Trap Never hold a lock across I/O, a network call or a long loop. Every thread that needs it waits.
Reentrant locks Take a lock you already hold Use a reentrant lock. It counts the owner's holds and frees the lock at zero.
Say what a plain mutex does then It deadlocks. The thread waits on itself forever.
Trap The inner call may see half-updated data from the outer one. Take the lock once, in the public method.
Read-write locks Let many readers in at once Use a read-write lock. Readers share it, and a writer holds it alone.
Say when it beats a mutex When reads far outnumber writes and each read holds it a while. Otherwise a mutex wins.
Trap A steady stream of readers can starve a writer, unless the lock prefers writers.
Spin or block Wait for a lock that may take a while Block. The OS parks the thread until the lock frees.
Wait for a lock held under a microsecond Spin, which skips a context switch. Real locks spin briefly, then block.
Trap Spinning on one core, or under heavy contention, starves the lock holder of CPU.
Deadlock & starvation when threads stop making progress Deadlock and lock ordering T1 lock B T2 lock A two orders T1 holds A and wants B. T2 holds B and wants A. The arrows close a cycle.T1 lock B T2 lock A one order Both take A first. T2 holds nothing while it waits, so T1 gets B and finishes.Define a deadlock Threads each wait for a lock another holds. They hang, and nothing crashes.
Name the four conditions Mutual exclusion, hold and wait, no preemption, circular wait. Break any one.
Prevent deadlock Take locks in one global order, say by ID. No cycle can form.
Back off when one order can't work lock.tryLock(50, TimeUnit.MILLISECONDS) On a timeout, release what you hold and retry.
Find a deadlock in a running JVM jstack <pid> It dumps every thread and reports any lock cycle.
Recover a deadlocked service Abort a victim, as databases do, or drain and restart.
Trap Lock ordering only works if every code path obeys it.
Livelock, starvation and priority inversion Define livelock Threads keep backing off and retrying in step. The CPU is busy, and nothing moves.
Break a livelock Add randomized backoff, or give one side priority.
Define starvation One thread waits forever while others keep winning the lock.
Stop a thread starving new ReentrantLock(true) A fair lock serves waiters in order, at some cost to throughput.
Define priority inversion A low-priority thread holds a lock a high one needs, and a medium one preempts it.
Prevent priority inversion Priority inheritance runs the holder at the waiter's priority.
Tip Mars Pathfinder kept resetting because of priority inversion.
Trap Priority inheritance helps only direct lock waits. It can't help a task stuck on I/O or a full queue.
Waiting & signaling sleep until something changes, or cap who gets in Condition variables Consumer, in Python with cond: takes the lock while not items: recheck after waking cond.wait() unlocks while asleep item = items.pop() Producer with cond: items.append(x); cond.notify()
Wait for a state change without spinning Wait on a condition variable. It sleeps until another thread signals.
Say why it pairs with a mutex Waiting releases the mutex as the thread sleeps. Waking takes it back.
Trap Recheck in a while, never an if. Wakeups can be spurious, or another thread got in first.
Semaphore or mutex Let at most 10 threads use a resource at once Semaphore slots = new Semaphore(10); acquire() waits when no permit is left.
Signal another thread Release a permit it waits on.
Trap A semaphore has no owner, so any thread can release it. As a lock, it loses that check.
Producer-consumer and bounded buffers Define the producer-consumer problem Producers fill a shared buffer and consumers drain it, at different speeds.
Block when the buffer is full or empty One mutex and two condition variables, not full and not empty.
Use a ready-made bounded buffer new ArrayBlockingQueue<>(100) put blocks when it's full, and take when it's empty.
Tip Two counting semaphores work too, one for free slots and one for filled ones.
Atomics & memory visibility when one thread sees another thread's writes Atomic operations Bump a counter without a lock hits.incrementAndGet(); Read, add and write happen as one step.
Say when one atomic is enough One variable, and each update stands alone, like a hit counter.
Say when you need a lock instead Two variables change together, or you act on a value later.
Tell another thread to stop volatile boolean stop; Java's volatile is enough. In C++ it isn't, so use std::atomic.
Trap Check-then-act on an atomic flag still races. Each access is atomic, but the sequence isn't.
Visibility and memory ordering Writer, in C++ data = 42; ready.store(true, std::memory_order_release); Reader if (ready.load(std::memory_order_acquire)) use(data); sees 42
Say why a thread misses another's write The write can sit in a core's store buffer, or the reader keeps a copy in a register.
Make a write visible Use a lock, an atomic or a memory barrier. Nothing else promises it.
Publish data to another thread Write the data, then do a release store to a flag.
Read the published data Do an acquire load of the flag. If it's set, the data is visible.
Say when relaxed ordering is safe Only when no thread relies on it to see other data.
Tip Caches stay coherent. The delay is in the store buffer.
Trap A missing acquire or release fails mostly on ARM. On x86, only the compiler can reorder those writes.
Double-checked locking and singletons Say how double-checked locking breaks The pointer can be published before the object's fields. Readers see it half-built.
Fix it in Java private static volatile Config instance; volatile adds the ordering it needs.
Lazy-init a singleton on the JVM Use a holder class. Class loading builds it once, and reads need no barrier.
Lazy-init a singleton in C++ static Config& get() { static Config c; return c; } The compiler makes its first init thread-safe.
Trap Broken double-checked locking passes every test on x86, then fails on ARM or after a compiler upgrade.
CAS & lock-free code update shared data without taking a lock Compare-and-swap Say what CAS does It writes the new value only if the old one is still there. If not, it fails and you retry.
Build a lock-free counter do { n = c.get(); } while (!c.compareAndSet(n, n + 1));
Define lock-free Some thread always makes progress, but one can retry forever.
Define wait-free Every operation finishes in a bounded number of its own steps.
Tip A stalled thread can't block either kind, as a stalled lock holder can. Wait-free is rarer and often slower.
Trap Under heavy contention, failed retries burn CPU and can lose to a plain lock.
ABA and freeing nodes thread 1 read A paused CAS ok thread 2 pop A pop B push A head A B C A B, freed time Thread 1 reads head A, then pauses. Thread 2 pops A and B, then pushes A back. Thread 1's CAS still sees A, succeeds, and sets head to freed node B.
Explain the ABA problem The value goes A to B to A between your read and your CAS, so the CAS succeeds.
Fix ABA with a version tag Pair the pointer with a counter bumped on every update. A double-width CAS swaps both.
Free a node no thread still reads Hazard pointers. Each thread publishes what it's reading, and reclaim skips those.
Free removed nodes in batches Use epochs. Free a batch once every thread has left that epoch. RCU works the same way.
Trap One stalled thread pins its epoch and stops all reclamation, so memory grows without bound.
A queue for many producers Build a lock-free queue The Michael-Scott queue. Producers CAS the tail forward.
Scale past one hot tail Shard into several queues. You lose one global order.
Shrink the contended step A ring buffer like the LMAX Disruptor. One CAS claims a slot.
Trap Every producer still fights over one tail cache line, so throughput flattens past a few cores.
Thread pools & backpressure reuse threads, and stop work piling up Thread cost and thread pools Know what a new thread costs Tens of microseconds to create and destroy, for the stack and kernel setup.
Know how big a thread's stack is 0.5 to 8 MB of reserved address space, depending on the platform.
Run many short tasks Executors.newFixedThreadPool(8) Workers reuse threads and pull tasks from one queue.
Size a pool About the core count for CPU work. More for I/O, where threads wait.
Tip A pool also caps concurrency. A burst waits in the queue, not as new threads.
Bounded queues and backpressure Say what an unbounded work queue does It hides overload. p99 climbs, GC pauses grow, then memory runs out.
Make a fast producer slow down Put a bounded queue between stages. When it's full, block, reject or drop.
Let the consumer set the pace Credit-based flow control. The consumer grants N items at a time.
Handle a source that can't slow down Shed load. You can't make network packets wait.
Tip Backpressure must reach the true source. One unbounded buffer upstream just moves the pile-up.
Trap Executors.newFixedThreadPool queues without bound. Pass a bounded queue to ThreadPoolExecutor .
Nested tasks in one pool Explain how nested tasks deadlock a pool Every worker waits on a subtask queued to the same pool. No thread is left to run it.
Avoid the deadlock Chain futures with continuations instead of blocking.
Block safely when you must Run the nested stage on a separate pool.
Tip A work-stealing pool can run the joined subtask on the waiting thread.
Trap A fixed pool of N threads deadlocks once tasks nest N deep.
Async & the event loop one thread, many waits The event loop Say how one thread runs async work The runtime does the waiting. Finished operations queue callbacks, and the loop runs one at a time.
Say when a ready callback runs After the current code returns and the stack is empty.
Know what a 200 ms sync call costs Every pending request waits 200 ms too, and timers fire late.
Name what usually blocks the loop Big JSON.parse calls, sync crypto, long loops, regex backtracking.
See a blocked loop before users do const h = monitorEventLoopDelay(); h.enable(); Import it from node:perf_hooks. It measures event-loop lag.
Trap A blocked loop misses health checks, so the orchestrator restarts the instance. Now it's a crash loop.
Async I/O or threads Serve thousands of slow network calls Async I/O. One thread juggles them, since waiting costs almost nothing.
Explain why async doesn't speed up CPU work Pure computation never waits, so there's nothing to yield.
Run CPU-heavy work in Node new Worker('./work.js') Worker threads run it on other cores.
Tip Use a thread pool for libraries that only block.
Trap Async spreads logic across callbacks and blurs stack traces. For modest concurrency, threads are simpler.
Cancel async work cleanly Cancel a request and everything under it fetch(url, { signal: ac.signal }) ac.abort() Pass one AbortController signal down to every child task.
Handle completion racing cancellation Settle exactly once. Guard it with a done flag.
Clean up on every exit Clear timers and listeners in finally. Make cleanup safe to repeat.
Trap A promise that never settles after abort hangs its awaiters forever. Reject pending waiters on cancel.
Actors instead of locks Share no state at all Use actors. Each owns its state and handles one message at a time.
Say when actors fit Many independent parts with their own state, as in Erlang or Akka.
Tip In Erlang, a supervisor restarts an actor that crashes.
Trap Actors can still deadlock through full mailboxes, and messages can arrive in a bad order.
Scaling under contention why more threads can make a program slower Contention and the knee Say why one lock caps throughput Threads queue for it. By Amdahl's law, the serial part caps speedup.
Say why more threads can be slower Each contended handoff costs a context switch and moves cache lines.
Spot a lock convoy Threads wake, miss the lock and sleep again. Lots of switching, little work.
Know a switch's cost under contention A few microseconds, plus tens more refilling caches.
Find the knee Plot throughput against threads. CPU-bound work often peaks near the core count.
Tip The Universal Scalability Law predicts a peak, then a decline, as coherency cost grows quadratically.
Trap Past the knee, adding threads lowers throughput. Shrink the critical section or shard the lock instead.
Lock granularity and striping Use one lock for the whole structure Coarse-grained. Simple and cheap, but every operation queues.
Use a lock per bucket or node Fine-grained. More parallel, but more memory, overhead and deadlock risk.
Stripe a hot lock locks[hash & mask].lock(); Use N locks, a power of two near the core count. The mask is one less than N.
Run an operation across all stripes Take the locks in index order. Sum per-stripe counts for size.
Scale a locked LRU cache Stripe the keys. Use clock eviction, since LRU writes on every hit.
Tip Start coarse. Measure where threads collide, and split only those spots.
Trap A single hot key still serializes on its one stripe.
Hot counters and false sharing Count across threads with little contention LongAdder hits = new LongAdder(); Each thread bumps its own cell. Reads sum them.
Know what reading it costs O(n) and slightly stale. Fine for metrics, wrong for hard limits.
Define false sharing Threads write different variables on one cache line, so the line bounces between cores.
Fix false sharing in C++ alignas(64) std::atomic<long> count; Give each hot variable its own 64-byte line.
Trap Unpadded per-thread cells false-share, and the contention you removed comes back.
Finding concurrency bugs catch races you can't reproduce on demand Diagnose and stress-test a race Turn a sometimes-wrong result into a trace Assert invariants on shared state. Log thread ID and time on failure.
Catch a race that didn't fire go test -race ./... A thread sanitizer flags unsynchronized access even if the output looks fine.
Make a rare interleaving happen Add random sleeps and yields. Run many threads on few cores.
Try every interleaving A model checker steps through the schedules one by one.
Reproduce production load Shadow or replay real traffic against an instrumented build.
Confirm a suspect variable Add a lock and watch the symptom vanish.
Trap Logging and sleeps change the timing. A passing instrumented run is weak proof.
Review code for concurrency bugs Start a review List shared mutable state. For each field, ask who writes it, who reads it, and under which lock.
Check fields that change together Change them under one lock, or a reader sees one without the other.
Find a deadlock by reading Flag any two locks taken in both orders across functions.
Tip Reason about the worst interleaving. Ask what happens if a switch lands on this exact line.
Nothing matches. Try a shorter word, or clear the search.
Practice Guided practice One question at a time, with the ones you miss coming back.
Practice 54 interview questions Full answers, from beginner to expert.
Found this useful? Pass it on.
Share on LinkedIn
Share on X
Copy link
This sheet stays free. With Pro , you can:
Every answer on this sheet. Read the full answer to all 54 questions it links, not just the 7 free ones.
My sheet. The cards you star and the ones you miss in Quiz me, from every sheet, on one page to quiz and print.
PDFs. Download this sheet and My sheet, laid out for A4, with every question a link to its answer.
The rest of Pro. Every answer in the question library, and 1,000 AI credits a month.