- aThreads in the same process share its memory; separate processes normally have their own address spaces✓
- bProcesses share memory by default, while each thread gets an isolated address space
- cBoth threads and processes always get fully isolated memory
- dThreads share memory only when they happen to run on the same CPU core
Backend Concurrency Interview Questions
The core of concurrency interviews: shared mutable state, concurrency versus parallelism, what locks really cost, deadlock/livelock/starvation, traps that appear at scale, and memory visibility.
Try the real simulation →Topic guide
What this topic actually tests
Concurrency questions are not about which API you have memorised. They test whether you can reason about shared, mutable state. The point interviewers return to most often: synchronization is needed only where state is both shared and mutable. Locals belong to each call, and data that never changes after startup can be read freely from as many threads as you like — immutability is the cheapest strategy for safe concurrency.
Distinctions that get confused
- Concurrency is not parallelism. Concurrency is about structure: interleaving work whose lifetimes overlap. Parallelism is about execution: work genuinely running at the same instant, which requires multiple cores. A single-core machine can be concurrent but never truly parallel.
- Process versus thread. A process owns its address space; its threads share that memory. That sharing is what makes communication cheap — and it is also exactly what makes shared-state bugs possible.
- A critical section is the region that touches shared state. The "critical" in the name has nothing to do with performance; the region's execution has to be made mutually exclusive.
- A race condition is not a kind of exception. Its defining property is timing dependence: the same inputs produce different results depending on how operations interleave. Races usually corrupt results silently, which is precisely what makes them dangerous.
The classic example is counter = counter + 1. Being one line in source means nothing: it is a read-modify-write sequence, and when two threads interleave between the read and the write, one increment overwrites the other and is lost.
The tools and what they really cost
A mutex promises exactly one thing: one owner at a time. It does not prevent preemption — the thread holding the lock can be paused mid-section; others simply cannot enter until it releases. Undo semantics belong to transactions, not to locks.
A single global lock buys correctness at the price of contention: under load threads queue on the lock while cores sit idle. A lock's cost comes from the number of threads that want it, not from how much data sits behind it. The usual remedies are finer-grained locks or less shared mutable state.
A read-write lock pays off when the workload is read-heavy and read sections are long enough for overlap to matter. It has two known costs: writer starvation (a writer can wait indefinitely while readers keep arriving; many implementations add writer preference) and extra bookkeeping — for very short sections a plain mutex is usually cheaper.
A counting semaphore is the right primitive for "at most N at a time". A mutex over-restricts it, and sleeping between calls shapes the request rate, not concurrency.
Pessimistic versus optimistic locking. Pessimistic excludes the conflict before it happens (SELECT ... FOR UPDATE); concurrent writers wait. Optimistic lets everyone proceed and catches the conflict at write time, typically with WHERE version = ?; the loser retries. Optimistic wins under low contention, pessimistic on hot, highly contended rows, where it prevents a retry storm.
Three ways progress stops
- Deadlock requires a cycle: each thread holds one lock while asking for the other. If everyone takes the locks in the same order (A then B), the cycle cannot form.
- In a livelock threads are not blocked but busy: they keep retrying, like two people in a corridor endlessly stepping aside for each other.
- Starvation is a fairness problem: the starved thread could make progress if its turn came, but it keeps losing the CPU or the lock to others.
Neither deadlock nor livelock reliably resolves itself; both need a design fix such as randomized backoff or consistent lock ordering.
Traps that only show up at scale
- Hot row. A row lock is held until the transaction commits, so every request queues behind one counter row and the system degrades into a serial pipeline. The fix is to shard the counter (N rows, increment a random one, sum on read) or move the increment to asynchronous aggregation.
- Thread pool deadlock. If parent tasks occupy every thread and then wait on subtasks that can only run in the same pool, you get a deadlock without a single mutex in sight. Fixes: run subtasks in a separate pool, do not block, or keep in-flight parents below the pool size.
- Blocking the event loop with CPU work. An event loop switches between tasks only where they yield, typically at I/O waits. Pure CPU work never yields, so every other connection stalls until it finishes. The standard fix is to move that work to workers.
- Scaling threads without counting cores. Eight cores run at most eight CPU-bound threads; switching among 800 threads spends the CPU on context switches instead of real work.
- False sharing. Independent variables that share a cache line make the coherence protocol ping-pong that line between cores: logically independent, physically contended. Fix by padding or by keeping thread-local accumulators and merging once at the end.
Visibility: locking is not the whole story
Correctness is not only mutual exclusion; the visibility of writes is a separate concern. happens-before is established by synchronization edges: an unlock ordered before a later lock of the same mutex, a release-store read by an acquire-load, or thread start/join. Merely sharing an address creates a data race, not an ordering, and wall-clock order means nothing without a synchronization edge.
Two practical consequences:
- Safe publication. The reference to a half-constructed object can become visible to other threads out of order. Publish it through an atomic carrying release-acquire semantics, or use the language's guaranteed one-time initialization facility.
whilearound a condition variable wait. Waking is a hint, not proof: spurious wakeups are allowed, and even after a real signal the woken thread must reacquire the mutex — in that gap another consumer can take the item. Rechecking the condition in a loop is the universal contract.
Duplicates and idempotency
A frequent branch of concurrency questions is "the same work happened twice". A disabled button on the client guarantees nothing, because retries and a second tab bypass it, and shrinking the window does not remove the race. The guarantee has to live on the server: a token per attempt, stored under a unique constraint, with repeats returning the original result. For state transitions the same job is done by a conditional update — UPDATE ... SET status = 'paid' WHERE id = ? AND status = 'pending' — acting only if a row actually changed.
What you should be able to explain in an interview
- Whether the state is genuinely shared and mutable; if not, no synchronization is needed at all.
- What your chosen primitive promises and what it does not (a mutex does not prevent preemption).
- How lock granularity affects contention, and the bill a single global lock runs up.
- How you made deadlock impossible (consistent ordering, one lock, bounded in-flight work).
- When a write becomes visible to another thread.
- What the system does when the same request arrives twice.
This guide is drawn from the verified explanations of the questions below.
Sample questions
- aThey mean exactly the same thing — the two words are interchangeable names for one concept
- bConcurrency is managing multiple overlapping tasks; parallelism is executing more than one at the same physical instant✓
- cParallelism can happen even on a single core, while concurrency always requires multiple cores
- dConcurrency applies only to I/O-bound work and parallelism only to CPU-bound work
counter = 0
function handle_request():
counter = counter + 1Two threads each call
handle_request() 1000 times. What is the final value of counter?- aAlways exactly 2000 — the increment is a single line, so it cannot be interrupted
- bAlways exactly 1000 — the second thread's writes overwrite the first thread's writes
- cAt most 2000, but possibly less — concurrent increments can be lost to a race condition✓
- dThe program crashes, because two threads may not write the same variable
counter = counter + 1 is a read-modify-write sequence. When two threads interleave between the read and the write, one increment overwrites the other and is lost, so the result can fall below 2000. Being a single line of source (a) means nothing — it compiles to multiple steps — and concurrent writes do not crash a program by themselves (d).- aThe most performance-critical hot path of the program, which should be optimized before anything else
- bCode that accesses a shared resource and must run in only one thread at a time✓
- cThe code that runs during application startup, before any threads exist
- dA block whose uncaught exceptions bring down the whole process
- aAt most one thread holds it at a time, so the code between lock and unlock never runs concurrently✓
- bThe code it protects is pinned to a single CPU core, making it faster
- cThe OS scheduler will not preempt the thread while it holds the lock
- dConflicting writes are detected and rolled back automatically
thread 1: thread 2:
lock(A) lock(B)
lock(B) lock(A)
# ... work ... # ... work ...
unlock(B) unlock(A)
unlock(A) unlock(B)What can happen when these two threads run at the same time?
- aThey always complete, because lock requests are granted in first-come-first-served order
- bA runtime error is raised: the same mutex cannot be requested from two different threads
- cLivelock: both threads endlessly acquire and release the locks without doing work
- dDeadlock is possible: each thread may hold one lock while waiting forever for the other✓
By specialization
By seniority
Other fields
Test yourself against the 3750-question Backend bank.
Start interview