Concurrency map

Concurrency in LLD — map

Shared-memory concurrency for interviews: threads vs processes, the primitive toolbox, language cheat sheet, and three problem families — correctness, coordination, scarcity.

When “same time” breaks your design

Concurrency is what happens when multiple things try to happen at once. Two users book the same seat. Three threads update one counter. A dozen requests hit a cache mid-refresh. Code that looked perfect in a single-threaded walkthrough suddenly produces impossible results under load.
It doesn’t show up in every LLD loop — company, team, and level decide. From senior onward it’s common either as the prompt or as a follow-up: the parking lot now has two cars racing for one spot; the inventory has two orders fighting over the last unit. Or the prompt is concurrency-first: thread pools, rate limiters, connection pools, schedulers.
01Atomics

One variable, CAS

02Lock

Critical sections

03Queue

Handoff + wait

04Semaphore

N permits

Analogy: one bathroom key

Mutex bathroom key
Only the key holder may enter the critical section.

A mutex is the single bathroom key at a gas station: whoever holds it can enter; everyone else waits. Atomics are a tiny lock for one dial. A queue is the take-a-number system so people don’t busy-wait at the counter.

Fundamentals: shared memory and interleaving

Check then act
Two threads both see “free” and both book.
Threads in the same process share memory. A process is an isolated address space; threads are independent execution paths with their own stacks and program counters, but they share the heap, globals, and open resources.
Concurrency means those threads can make progress independently and their instructions can interleave. On multiple cores they may run truly in parallel; on one core the OS switches rapidly. From your code’s point of view both look the same: operations from different threads can interleave unpredictably.
That unpredictability is the bug factory. Source that “looks atomic” is often several machine instructions. Without coordination, outcomes depend on timing and load — nondeterministic and hard to reproduce. Assume concurrency whenever shared mutable state exists.

The toolbox (quick reference)

You don’t invent new sync mechanisms — you recognize which existing primitive fits. Each is covered in depth in the Correctness / Coordination / Scarcity posts.
  • Atomics — thread-safe ops on one variable (CAS). Counters, flags. Fail when two fields must stay consistent. → Correctness.
  • Locks (mutexes) — mutual exclusion for a critical section. Default for check-then-act and multi-field updates. Coarse / fine / read-write. → Correctness.
  • Semaphores — counting permits. Cap concurrent downloads or in-flight API calls. → Scarcity.
  • Condition variables — wait efficiently until a condition is true (release lock + sleep; wake on signal). Foundation for blocking queues; rarely coded raw in interviews. → Coordination.
  • Blocking queues — thread-safe put/take; block when full/empty. Default for producer-consumer and for pooling actual resource objects. → Coordination & Scarcity.
from threading import Lock, Semaphore
from queue import Queue
from concurrent.futures import ThreadPoolExecutor  # often enough in Python

# Atomics: Python has no AtomicInteger — use a Lock, or multiprocessing.Value
# Lock
lock = Lock()
with lock:
    balance += amount

# Semaphore — at most 5 concurrent ops
slots = Semaphore(5)

# Blocking queue — capacity enables backpressure
q: Queue = Queue(maxsize=1000)
q.put(task)   # blocks if full
task = q.get()  # blocks if empty
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;

// Atomics
AtomicInteger balance = new AtomicInteger(0);
balance.addAndGet(amount);

// Lock
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
    // critical section
} finally {
    lock.unlock();
}

// Semaphore — at most 5 concurrent ops
Semaphore slots = new Semaphore(5);

// Blocking queue — capacity enables backpressure
BlockingQueue<Runnable> q = new ArrayBlockingQueue<>(1000);
q.put(task);   // blocks if full
Runnable t = q.take();  // blocks if empty

// Thread pool
ExecutorService pool = Executors.newFixedThreadPool(8);

Three problem types

Three types
Correctness · Coordination · Scarcity — same primitives, different failure modes.
Surface domains change (inventory, booking, rate limiters) but failure modes don’t. Seeing past the domain into the problem type is the interview skill.
  • Correctness — shared state corrupted. Two threads book one seat. Fix: locks, atomics, thread confinement. Patterns: check-then-act, read-modify-write.
  • Coordination — threads need ordering or handoff. Producers enqueue; consumers wait without spinning. Fix: blocking queues, actors, event loops.
  • Scarcity — resources are limited (10 connections, 100 requests). Fix: semaphores, resource pools.
Most questions start with correctness. Coordination and scarcity show up as follow-ups once shared state exists or throughput rises. Real systems mix them; separating them keeps reasoning clean.

Worked mini-example: two bookers, one seat

# Broken
if seat.free:
    seat.free = False  # race: both threads pass the check

# Fixed (coarse)
with show.lock:
    if not seat.free:
        raise RuntimeError("taken")
    seat.free = False
// Broken
if (seat.free) {
    seat.free = false;  // race: both threads pass the check
}

// Fixed (coarse)
synchronized (show.lock) {
    if (!seat.free) {
        throw new IllegalStateException("taken");
    }
    seat.free = false;
}

This is the entire reason concurrency appears in LLD: check-then-act on shared mutable state.

Concurrency basic anti-patterns

  • “Java is thread-safe” as a design.
  • Synchronizing every getter.
  • Starting with lock-free atomics before a mutex version.
  • Discussing CPUs/cores instead of the invariant.

Map checklist

  1. Is the problem correctness, coordination, or scarcity?
  2. What is the shared mutable state?
  3. What is the invariant in one sentence?
  4. What’s the coarsest lock that works for v1?

Common interview pitfalls

These mistakes show up constantly on this prompt. Name the trap, then show the fix in your design — don’t wait for the interviewer to catch you.

  • Jumping to scarcity/coordination without shared-state invariant.
  • “Immutable everything” as a dodge when mutation is required.
  • Confusing parallelism speedup with race freedom.
  • No example problem — only definitions.
  • Saying threads are bad / async is always better.

Interview script (say this)

Read this once out loud before a mock. It’s the spine of a strong answer — not a script to recite robotically.

  1. Concurrency means overlapping execution touching shared state.
  2. Three families: correctness, coordination, scarcity.
  3. I’ll always state the invariant first.
  4. Example: two bookers, one seat — check-then-act.
  5. v1 tool is a mutex around the critical section.
  6. Then we can discuss queues or pools if the prompt needs them.
  7. I map the follow-up to the right family before naming primitives.
  8. Basics page is the map; deep dives are the drills.

Extra verification traces

Walk these three traces on the board. If you can narrate them cleanly, your implementation section usually follows.

Staff-level follow-ups

At staff+, they twist the prompt. Answer in one sentence that names the seam — don’t redesign the whole board.

  • Where to go deeper? — Correctness first, then coordination/scarcity hubs.
  • Language specifics? — Speak invariants; map to synchronized/Lock/asyncio as needed.
  • Async only shop? — Same races exist across tasks — locks or confinement still apply.

Interleaving worksheet (draw this)

Interviewers love a two-column timeline. Practice this exact race until you can draw it in 30 seconds for any check-then-act prompt.
# Shared: seat = AVAILABLE
# Alice                         Bob
# read seat → AVAILABLE
#                               read seat → AVAILABLE
# write seat = BOOKED(Alice)
#                               write seat = BOOKED(Bob)   # last write wins
# return True                   return True
# → both think they own the seat; only Bob is in memory

# Fixed: one lock around check+write
# Alice                         Bob
# acquire lock
# read AVAILABLE → book Alice
# release lock
#                               acquire lock
#                               read BOOKED → return False
#                               release lock
// Shared: seat = AVAILABLE
// Alice                         Bob
// read seat → AVAILABLE
//                               read seat → AVAILABLE
// write seat = BOOKED(Alice)
//                               write seat = BOOKED(Bob)   // last write wins
// return true                   return true
// → both think they own the seat; only Bob is in memory

// Fixed: one lock around check+write
// Alice                         Bob
// acquire lock
// read AVAILABLE → book Alice
// release lock
//                               acquire lock
//                               read BOOKED → return false
//                               release lock

Complete mini-solution: seat booking

A full interview-sized class you can adapt to parking spots, locker bays, or inventory units. Coarse lock is the default; comment the upgrade path.
from threading import Lock
from typing import Optional

class SeatBooking:
    def __init__(self, seat_ids: list[str]):
        self._owners: dict[str, str] = {}          # seat → user
        self._valid = set(seat_ids)
        self._lock = Lock()

    def book(self, seat_id: str, user_id: str) -> bool:
        if seat_id not in self._valid:
            raise ValueError(f"unknown seat {seat_id}")
        with self._lock:
            if seat_id in self._owners:
                return False
            self._owners[seat_id] = user_id
            return True

    def cancel(self, seat_id: str, user_id: str) -> bool:
        with self._lock:
            if self._owners.get(seat_id) != user_id:
                return False
            del self._owners[seat_id]
            return True

    def owner_of(self, seat_id: str) -> Optional[str]:
        with self._lock:
            return self._owners.get(seat_id)

# Upgrade path (say aloud): dict[seat_id, Lock] + ordered multi-seat
# acquire to avoid deadlock — only if coarse lock becomes a hotspot.
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;

class SeatBooking {
    private final Map<String, String> owners = new HashMap<>();  // seat → user
    private final Set<String> valid;
    private final ReentrantLock lock = new ReentrantLock();

    SeatBooking(List<String> seatIds) {
        this.valid = new HashSet<>(seatIds);
    }

    boolean book(String seatId, String userId) {
        if (!valid.contains(seatId)) {
            throw new IllegalArgumentException("unknown seat " + seatId);
        }
        lock.lock();
        try {
            if (owners.containsKey(seatId)) return false;
            owners.put(seatId, userId);
            return true;
        } finally {
            lock.unlock();
        }
    }

    boolean cancel(String seatId, String userId) {
        lock.lock();
        try {
            if (!userId.equals(owners.get(seatId))) return false;
            owners.remove(seatId);
            return true;
        } finally {
            lock.unlock();
        }
    }

    String ownerOf(String seatId) {
        lock.lock();
        try {
            return owners.get(seatId);
        } finally {
            lock.unlock();
        }
    }
}

// Upgrade path (say aloud): ConcurrentHashMap + per-seat ReentrantLock
// with ordered multi-seat acquire to avoid deadlock — only if coarse lock is a hotspot.

Which classic needs which lock?

  • Parking / locker / library copy — coarse lock on the bank/lot (find + claim). → Correctness.
  • Movie / BookMyShow seats — per-show lock; never across payment. → Correctness + hold TTL.
  • Rate limiter — lock per client key (or shard); refill+consume atomic. → Correctness.
  • Inventory transfer — ordered multi-lock (warehouse id ascending). → Correctness (deadlock).
  • Logger async — bounded queue + worker. → Coordination.
  • Connection / thread pool — semaphore or blocking queue of resources. → Scarcity.
  • Chess / Tic-Tac-Toe / Connect Four — usually single-threaded game; if HTTP API, one lock per Game id.

← Lattice