Menu

Earn Premium with Referrals

Invite your friends and earn Premium rewards through our referral program.

See how it works and start inviting friends.

LLD Workflow
LLD

LLD Workflow

A practical workflow for approaching Low-Level Design problems from requirements to implementation.

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

CategoryExample questionDesign impact
Functional coreWhich operations must work end-to-end?Public API surface
Data / entitiesWhat is remembered forever vs transiently?Entity vs stateless-service split
ScaleRequests/sec? Item count? Users?Naive structures vs indexed ones
ConcurrencyCan two actors mutate the same object simultaneously?Locking strategy, thread-safe collections
PersistenceSurvive restart?In-memory map vs repository interface
Explicit non-goalsPayments, 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

  1. “What are the core entities, and who creates them?”
  2. “What’s the read/write ratio?” — shapes caching and structures.
  3. “Should this be thread-safe from minute one?”
  4. “Is persistence required, or is losing state on crash acceptable?”
  5. “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 typeTestDesign elementExample
EntityIdentity matters; two instances with identical fields still differ (id)Class with identity fieldBooking, User
Value objectNo identity; interchangeable when equalImmutable class; equals/hashCode on fieldsMoney, Address, TimeSlot
Aggregate rootOwns children’s lifecycle; sole mutation entry pointClass guarding its invariantsOrder owns OrderLines
Enum / constantFixed closed set of valuesenumVehicleType, PaymentStatus
Pure processVerb with no natural ownerService 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() over paymentService.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 (Show holds List<Seat>), never a Seats class.

Failure Modes

  • Anemic model: every noun is a field bag; one service holds all operations — fails encapsulation scoring outright.
  • Identity confusion: mutable Money entity breaks equality-based deduplication in maps/sets.
  • Missing enum: status as String compiles invalid states and detonates at runtime. Closed sets are always enums.
  • Verb-as-class disease: Booker, Confirmer, Canceller classes — 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, not long amountInPaise — units and validation live in exactly one place.
  • Return results, not nulls: Optional<Slot> or empty list; returning null transfers your bug to every caller.
  • Immutable outputs: return List.copyOf(items); a returned mutable collection lets callers corrupt internal state.
  • Narrow exceptions: checked InsufficientFundsException documents 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.

My Private Notes

Notes are auto-saved locally to this device.