Concurrency Interview Questions: The Failure Modes and Patterns Companies Actually Test

Prepare for concurrency interviews by tracing races, deadlocks, queues, lock choices, retries, and distributed atomicity through practical examples.

Author: PracHub

Published: 8/14/2026

Concurrency Interview Questions: The Failure Modes and Patterns Companies Actually Test

August 14, 2026
19 min read
Concurrency Interview Questions: The Failure Modes and Patterns Companies Actually Test

Quick Overview

Learn a repeatable concurrency interview method: identify shared state, state the invariant, then choose the smallest safe coordination mechanism. Covers races, deadlocks, blocking queues, idempotency, and distributed contention.

Software EngineerFree

Concurrency questions are easier once you identify what can overlap and what must remain true while it does. A Software Engineer may be asked to protect an in-memory transfer, coordinate producers and consumers, or explain how two requests could reserve the same last item. These prompts use different tools, but they test the same habit: name the shared state, state the invariant, then choose the smallest mechanism that protects it.

This guide focuses on that reasoning path. It separates thread-safety problems from interval problems, explains the four failure modes worth naming, and works through the implementation patterns that recur in coding and system design rounds.

Read the prompt before reaching for a lock

“Concurrent” can describe three different situations. Only one automatically calls for synchronization primitives.

Prompt shapeWhat overlapsFirst tool to consider
Several threads update one objectReads and writes to shared mutable stateLock, atomic operation, immutability, or single ownership
Tasks use separate data but should run at the same timeCPU or I/O workThread pool, process pool, coroutine, or worker queue
Intervals overlap on a timelineTrips, meetings, sessionsSweep line or sorted endpoints

Concurrency means several tasks can make progress during the same period. Parallelism means work actually executes at the same instant, usually on different cores. A web server can be highly concurrent on one core because tasks take turns while waiting for I/O. Sorting independent chunks on several cores is parallel because those chunks execute simultaneously.

The distinction matters because a lock only protects shared state. It does not make CPU work faster, and it is irrelevant to a “maximum overlapping meetings” problem.

Decision map for classifying concurrency interview prompts What can overlap? Shared state Protect the invariant locks · atomics · ownership Independent work Choose an executor threads · processes · async Time intervals Sort the boundaries sweep line · prefix count
Classify the overlap first. The vocabulary in the prompt does not determine the algorithm.

Before writing code, say the invariant in one sentence. For a transfer it might be “the debit and credit happen atomically, and total money is conserved.” For a bounded queue it is “the size stays between zero and capacity.” That sentence tells you what must be protected and how large the critical section needs to be.

Four failure modes and their fixes

Interviewers rarely ask for definitions in isolation. They show a loop or an interleaving and ask what can go wrong. Use the names below only after you can point to the exact loss of progress or correctness.

Failure modeWhat happensTypical causeCommon fix
Race conditionThe result depends on an unsafe interleavingRead-modify-write without one atomic boundaryLock the whole invariant or use a suitable atomic primitive
DeadlockEvery participant waits foreverA cycle in lock acquisitionGlobal lock order, fewer locks, or a timeout plus recovery
LivelockThreads keep reacting but no work completesSymmetric retry-and-release behaviorRandomized backoff or deterministic ownership
StarvationThe system progresses while one participant never doesUnfair scheduling or reader preferenceFair queueing or an explicit handoff policy

Consider two transfers running in opposite directions. If each thread locks its source account first, one can hold account A while waiting for B and the other can hold B while waiting for A.

Deadlock cycle caused by opposite lock acquisition order Thread 1 holds A · waits for B Thread 2 holds B · waits for A waits for lock B waits for lock A
A stable acquisition order removes the cycle: every transfer locks the lower account ID first.

A global order is the cleanest repair:

def transfer(accounts, source_id, target_id, cents):
    if cents <= 0 or source_id == target_id:
        raise ValueError("invalid transfer")

    first_id, second_id = sorted((source_id, target_id))
    with accounts[first_id].lock:
        with accounts[second_id].lock:
            source = accounts[source_id]
            target = accounts[target_id]
            if source.balance < cents:
                raise ValueError("insufficient funds")
            source.balance -= cents
            target.balance += cents

The code protects one multi-object invariant. Replacing both locks with separate atomic balance updates would not be enough, because another thread could observe the debit before the credit.

Implementation patterns worth knowing

The strongest preparation is not memorizing a large concurrency library. It is knowing a few patterns well enough to explain why each line exists.

Bounded blocking queue

A producer waits while the queue is full; a consumer waits while it is empty. Both conditions share one lock because the queue state is one invariant.

import threading
from collections import deque

class BoundedQueue:
    def __init__(self, capacity):
        if capacity <= 0:
            raise ValueError("capacity must be positive")
        self.capacity = capacity
        self.items = deque()
        lock = threading.Lock()
        self.not_empty = threading.Condition(lock)
        self.not_full = threading.Condition(lock)

    def put(self, item):
        with self.not_full:
            while len(self.items) == self.capacity:
                self.not_full.wait()
            self.items.append(item)
            self.not_empty.notify()

    def get(self):
        with self.not_empty:
            while not self.items:
                self.not_empty.wait()
            item = self.items.popleft()
            self.not_full.notify()
            return item

The while is essential. A wakeup does not guarantee the condition is still true when the thread reacquires the lock. Another consumer may have taken the item first, and some runtimes permit spurious wakeups. Both operations are O(1); storage is O(capacity).

Lock, atomic, immutable state, or one owner?

MechanismBest fitImportant limit
MutexAn invariant spans several reads and writesContention grows with the critical section; multiple locks require an order
Atomic operationOne counter, flag, or reference can change independentlySeparate atomics do not protect a multi-field invariant
Immutable snapshot plus atomic swapReaders need a consistent whole-object viewCopying can be expensive on write-heavy paths
Single owner plus messagesOne worker can serialize all mutationsAdds a queue hop and makes overload visible as backlog

A token bucket illustrates the choice. Its token count and last-refill time must move together. A small per-bucket lock is often clearer than attempting separate atomic updates. The algorithm trade-offs are covered in the rate-limiting guide.

The following university lecture gives a deeper treatment of interleavings and synchronization primitives:

Concurrency at system boundaries

In system design, the same race appears across database transactions and network retries instead of threads.

Suppose two requests try to reserve the last parking space. Both read available = true, then both write available = false. A mutex inside one API process cannot protect the system when requests reach different processes. The atomic boundary has to move to shared storage:

UPDATE parking_spaces
SET available = false
WHERE id = :space_id
  AND available = true;

Exactly one request should observe one affected row. The other must choose another space or report that capacity is gone. The parking-lot design guide follows this race through a complete backend design.

Retries create another concurrency problem. A server can commit a charge while its response is lost, causing the client to retry an operation that already succeeded. An idempotency key gives both attempts one logical identity, but the key check and result write must be atomic. Otherwise the race moves into the deduplication table. See why idempotency is only half the answer for the storage and recovery details.

Use this four-step explanation in a design discussion:

  1. Name the competing operations.
  2. State the invariant they could violate.
  3. Place the atomic boundary in the component shared by every contender.
  4. Explain the losing path: retry, reject, or compensate.

That sequence is more useful than saying “use a lock,” because it also works for unique constraints, compare-and-set updates, leases, and transactional queues. The technical interview rubric explains how to make that reasoning visible while you code.

FAQ

Does Python’s GIL make a program thread-safe?

No. The GIL limits simultaneous Python-bytecode execution in the standard interpreter, but a logical operation can still span several bytecode instructions and release points. Shared mutable state still needs a synchronization strategy. Threads are useful for I/O-bound concurrency; process pools are a common choice for CPU-bound parallel work.

Why must condition-variable waits use while instead of if?

A wakeup only means the thread should check again. The condition may no longer hold by the time it reacquires the lock, and a wakeup may be spurious. The loop restores the invariant before the operation continues.

Is lock-free code always faster?

No. Under contention, compare-and-set retries can burn CPU and still make poor progress. Lock-free algorithms also cost more to reason about and test. Prefer the simplest mechanism that meets the measured latency and throughput requirement.

How should I practice concurrency questions?

Start with one bounded queue, one ordered-lock transfer, and one database compare-and-set update. For each, trace two competing operations by hand and identify the invariant at every step. That practice transfers better than memorizing isolated definitions.


Comments (0)