How to approach any new LLD problem from scratch.
In DSA you go Brute β Better β Optimal by removing repeated work. In LLD you go Vague prompt β Naive MVP β Pain point β Clean design by finding what changes and hiding it behind the right boundary.
The 5-step thinking process (use this in every interview)
Clarify & scope the 45-minute target
Interview prompts are vague on purpose ("Design BookMyShow", "Design an ATM"). Never jump straight into classes. Ask a few focused questions to fix the boundary:
- β’
Core happy path: which 2β3 methods will the caller use 90% of the time?
- β’
Failure & edge cases: invalid transitions, not enough stock, partial failure?
- β’
Concurrency: single-threaded in memory, or do threads race for the same thing (seat locking, rate limiting)?
- β’
Out of scope: you design in-memory classes and interfaces β not SQL schemas, REST controllers, or UI.
Extract the nouns β and reject the false classes
Underline every noun in the requirements and put each one in a bucket. Beginners fail by turning every noun into a heavy class:
- β’
Value struct: plain data with no rules (
Ticket,Money,Coordinates,LogMessage). - β’
Stateful class / manager: owns invariants and transitions (
Order,SeatLockManager,AtmMachine). - β’
Interface: anything with more than one implementation (
PricingStrategy,PaymentGateway,EvictionPolicy). - β’
Not a class: trap nouns like
Price,Timestamp,Statusbecome fields,enum classvalues or return types.
Sketch the naive MVP and ask "what breaks tomorrow?"
Picture the simplest single-class version first, then stress-test it against change. A pattern is only ever the cure for a concrete smell:
- β’
Long
if/elseon which algorithm? Every new rule edits the core class (breaks Open-Closed). - β’
Boolean flags (
isPaid_,isCancelled_)? Illegal combinations compile and cause bugs. - β’
One method calls 4 subsystems directly? Impossible to test without real gateways and databases.
Map only the real hotspots to patterns
Add an interface or pattern only where something genuinely varies. In most interviews 2β3 patterns working together is the sweet spot:
- β’
Pluggable rule or calculation β Strategy (A1) injected through the constructor.
- β’
Strict lifecycle with guarded transitions β State (A4).
- β’
One event, many independent reactions β Observer (A3).
- β’
Multi-step workflow β Facade (A9), or Command (B4) when you need undo / rollback.
Write modern C++23 β ownership, invariants, locks
Turn the UML boxes into clean C++23 that compiles and proves its invariants in `main()`:
- β’
Ownership (F5):
std::unique_ptr<Interface>for owned polymorphic parts, value members by default, references for borrowing. No rawnew/delete. - β’
Errors (F7): return
std::expected<T, Error>orstd::optional<T>for normal rejections, not-1or exceptions. - β’
Concurrency: guard shared state with
std::mutex+std::scoped_lock, and never hold a lock across slow I/O such as a payment call.
Pain point β design pattern decision tree
Don't memorise patterns one by one. Start on the left with the pain hurting your naive code, follow the branch that matches, and open the chapter that fixes it.