How to handle concurrency scenarios
Concurrency shows up in LLD interviews as a follow-up question more often than as the main prompt: you finish a design for a parking lot, a rate limiter, or a ticket booking system, and the interviewer asks "what happens if two requests hit this at the same time?" This page is about recognizing that moment and responding to it - not about implementing a concurrent-correctness proof live, which almost nobody expects in the time given. The Clean Code module has a concurrency chapter covering the discipline of writing concurrent code well in a real codebase; this page is narrower and interview-shaped.
Recognize the shape of the problem first
The tell is always the same: some piece of state is shared across requests or threads, and more than one of them can mutate it. A few shapes come up again and again:
- A shared counter or ID generator. A ticket-booking system's "next seat number" or a URL shortener's "next short code" - if two requests read the current value, increment it, and write it back without coordination, both can read the same starting value and step on each other, producing a duplicate.
- A Singleton with mutable state. The moment a Singleton in your design (per choosing design patterns) holds anything other than read-only config - a cache, a counter, a connection pool's list of leased connections - it's shared mutable state by construction, because every caller in the process reaches the same instance.
- A shared queue between producers and consumers. A ride-sharing dispatch queue, a print spooler, a task scheduler - anything where one component adds work and another removes it needs the add and remove operations themselves to be safe against interleaving, on top of whatever ordering logic you designed.
- A limited resource being allocated. A parking lot's spot allocation, a movie theater's seat booking - two customers requesting the last spot at the same instant is the same race as the shared counter, just dressed as inventory.
The habit worth building: whenever you introduce a class that holds mutable state and could plausibly be called from more than one thread or request at once, ask yourself the question before the interviewer does.
Try it yourself: a food-delivery app's DeliveryZone class keeps a running
activeRiders count that goes up when a rider accepts an order and down when they drop
offline, and both events can arrive from separate requests at once. Spot the risk here before
reading on, and name the fix you'd reach for.
Name the tool that fits, at a conceptual level
You don't need to write correct lock-free code live. You need to correctly diagnose the race and name a standard, appropriately-scoped fix:
- A lock or mutex around the critical section. The read-increment-write sequence on a
shared counter needs to happen as one atomic unit; wrapping it in a lock (or the language's
equivalent -
synchronizedin Java, aLockobject, a mutex) is the standard answer, and naming it is usually enough. - An atomic primitive for simple counters. If the entire critical section is "increment this number," most languages offer an atomic integer type that does the increment atomically without an explicit lock - a lighter-weight answer than a full mutex for exactly this case.
- A thread-safe queue for the producer-consumer shape. Reach for whatever the language's standard library already offers (a blocking queue, a channel) rather than hand-rolling synchronization around a plain list - this is the same "don't reinvent what the platform already gives you" instinct that applies everywhere else in design.
- Optimistic checks for resource allocation. For something like the last-parking-spot race, a common pattern is to attempt the allocation under a lock, re-check availability inside that lock, and fail the second request cleanly rather than let both succeed.
Match a few race scenarios to the fix that fits:
A URL shortener's nextCode counter is read, incremented, and written back by two requests at the same moment, and the entire critical section is just that one increment.
Say what you noticed, not that you solved it
The honest, and usually sufficient, answer to "what happens under concurrent access" is a short sentence: "this counter's increment isn't atomic, so I'd put it behind a lock (or use an atomic type) to make the read-modify-write a single step." That sentence demonstrates you spotted the race and know the standard-issue fix for it. Interviewers running an LLD round are almost never expecting you to produce a fully proven, deadlock-free, lock-free concurrent design in the minutes remaining - that's a distinct skill with its own interview format. Being honest about that boundary ("I'd want to think harder about lock ordering here if this grows more shared state") reads as calibrated, not as a gap.