Why This Phase Decides the Round
Most failed LLD designs fail before any class exists: they solve a problem the interviewer never asked. “Design a parking lot” contains at least four different systems depending on answers about vehicle types, floors, pricing, and concurrency — and each answer changes the model. Clarification converts a vague sentence into a contract you can be graded against fairly.
"Design a parking lot" (vague statement)
│
▼ functional ───────── vehicle types? slot allocation?
▼ scale ────────────── 1 lot? multi-floor? entry gates?
▼ persistence? ─────── restart-safe or in-memory OK?
▼ concurrency? ─────── parallel entries at gates?
▼ out of scope ─────── payments? sensors? mobile app?
│
▼
3–6 locked constraints written down (design contract)
Each branch prunes possible designs. By the bottom, both interviewer and candidate hold the same system in mind — every later grading dispute can be settled by pointing at this list.
Question Taxonomy
| Category | Example question | Design impact |
|---|---|---|
| Functional core | Which operations must work end-to-end? | Public API surface |
| Data / entities | What is remembered forever vs transiently? | Entity vs stateless-service split |
| Scale | Requests/sec? Item count? Users? | Naive structures vs indexed ones |
| Concurrency | Can two actors mutate the same object simultaneously? | Locking strategy, thread-safe collections |
| Persistence | Survive restart? | In-memory map vs repository interface |
| Explicit non-goals | Payments, auth, UI needed? | Prevents minutes lost to unasked scope |
Time Discipline
- Budget: ≤ 5 minutes. Rubrics score the phase but penalize over-spending.
- Batch questions (“scale and concurrency — may I assume X and Y?”), don’t ask one-per-turn.
- Restate every answer as a locked constraint: “So: single lot, cars+bikes, hourly pricing, in-memory fine.”
The Five High-Leverage Questions
- “What are the core entities, and who creates them?”
- “What’s the read/write ratio?” — shapes caching and structures.
- “Should this be thread-safe from minute one?”
- “Is persistence required, or is losing state on crash acceptable?”
- “What’s explicitly out of scope?”
Failure Modes
- Assuming silence: when the interviewer says “you decide,” stating the assumption aloud still earns credit — wrong-but-stated beats hidden.
- Scope creep: designing payment integration nobody asked for kills the core’s finishing time.
- Under-asking: skipping the concurrency question collapses the design at follow-up: “now two gates admit the same spot.”
- Not writing constraints down: by minute 30 candidates contradict their own earlier answers.
The Problem
Given a paragraph of requirements, beginners either freeze (“where do classes come from?”) or transcribe every noun into a class (producing fifteen anemic field-bags). Entity identification is a repeatable extraction procedure, not inspiration.
Noun–Verb Extraction
"User books a seat for a show; payment confirms the booking."
│ │
nouns = class candidates verbs = behavior owners
│ │
▼ ▼
User, Seat, Show, book() → whose state changes? → Booking/Show
Payment, Booking confirm() → what becomes valid? → Booking
Nouns propose state holders; verbs propose operations — and each verb belongs to the class whose invariants it guards. confirm() turns “provisional” into “confirmed,” which is a Booking invariant, so the method lives on Booking — not on a standalone PaymentService by default.
Classification Table
| Noun type | Test | Design element | Example |
|---|---|---|---|
| Entity | Identity matters; two instances with identical fields still differ (id) | Class with identity field | Booking, User |
| Value object | No identity; interchangeable when equal | Immutable class; equals/hashCode on fields | Money, Address, TimeSlot |
| Aggregate root | Owns children’s lifecycle; sole mutation entry point | Class guarding its invariants | Order owns OrderLines |
| Enum / constant | Fixed closed set of values | enum | VehicleType, PaymentStatus |
| Pure process | Verb with no natural owner | Service class (rare — justify) | PricingCalculator |
Worked Progression: BookMyShow
- Toy: nouns = User, Movie, Seat → three classes, works for demo.
- Realistic: add Booking (aggregate root), Show (owns Seat inventory), Payment (entity), PaymentStatus/MovieGenre (enums) → ~7 core classes.
- Production: same shapes plus repositories behind interfaces and a lock manager for seat holds — structure unchanged, infrastructure added underneath.
The stable observation across all three: the domain classes barely change as scale grows. That stability is the point of identifying entities correctly.
Ownership Rules
- A method lives with the state it mutates:
booking.confirm()overpaymentService.confirmBooking(booking). - Two candidate owners → pick the one whose invariants the method could break.
- Cross-entity workflows get exactly one orchestrator (
BookingManager.book()) — orchestration is legitimate; doing everything is not.
Sanity Checks
- Most LLD problems yield 4–8 core classes. Fifteen at minute ten means modeling implementation details.
- Every entity answers: what state, which invariants, who may mutate me.
- Plural nouns (“seats”) → collection owned by an aggregate (
ShowholdsList<Seat>), never aSeatsclass.
Failure Modes
- Anemic model: every noun is a field bag; one service holds all operations — fails encapsulation scoring outright.
- Identity confusion: mutable
Moneyentity breaks equality-based deduplication in maps/sets. - Missing enum: status as
Stringcompiles invalid states and detonates at runtime. Closed sets are always enums. - Verb-as-class disease:
Booker,Confirmer,Cancellerclasses — verbs are methods, not types.
Why Contracts Come First
Code organized implementation-first couples callers to whatever exists today. When the storage or vendor changes tomorrow, every caller breaks with it. Interface-first reverses the dependency: the contract is written before any implementation, callers bind to the contract, and implementations become replaceable parts.
Caller code Interface (contract) Implementations
┌─────────────────┐ ┌──────────────────────────────┐ ┌──────────────────┐
│ depends only on │──────► │ pay(PaymentRequest) │◄─────│ CardProcessor │
│ the contract │ │ : PaymentResult │ │ UpiProcessor │
└─────────────────┘ │ throws InsufficientFundsEx. │ │ WalletProcessor │
└──────────────────────────────┘ └──────────────────┘
New implementation = new file. Existing callers untouched (Open-Closed in practice).
The arrows matter more than the boxes: callers point at the interface only; implementations plug in from below. Nothing on the left side names a concrete class — that is the whole property being purchased.
Signature Rules
- Domain types over primitives:
Money amount, notlong amountInPaise— units and validation live in exactly one place. - Return results, not nulls:
Optional<Slot>or empty list; returningnulltransfers your bug to every caller. - Immutable outputs: return
List.copyOf(items); a returned mutable collection lets callers corrupt internal state. - Narrow exceptions: checked
InsufficientFundsExceptiondocuments recoverable cases; unchecked for programming errors. - Names are part of the contract: verb-noun commands (
book()), predicates for booleans (isAvailable()).
Good vs Bad
// Bad: primitives, nulls, silent failure codes
int doPayment(long amt, String mode); // -1 means failure?
// Good: types carry meaning, failure explicit
PaymentResult pay(Money amount, PaymentMode mode) throws InsufficientFundsException;
Evolution Cost
- Changing a public signature = compile-time break of all callers — visible and forced.
- Changing behavior while keeping the signature = runtime contract drift — invisible until production.
- Therefore signatures deserve early care; parameters grow via overloads or parameter objects (
PaymentRequest) rather than arg lists past ~3.
Failure Modes
- Leaky interface:
process(Map<String,Object> ctx)— callers couple to map keys; refactors break them silently at runtime. - Interface with one impl and no seam purpose — indirection without benefit unless a test seam or planned variant exists.
- God interface: twenty methods on
ParkingLotService— every implementer stubs methods it does not need (ISP violation). - Checked-exception abuse: forcing try/catch around unrecoverable system failures converts logic to boilerplate.
Premium Content
Unlock LLD Workflow and all premium lessons with a subscription.
From ₹199.99/year — See plans