Foundation 6 of 6 · 20 min

The LLD Interview Playbook

A repeatable 45-minute structure, what interviewers actually score, and the mistakes that cost points.

✓ Run an LLD round with a fixed structure✓ Know where to spend the time and where to stop✓ Check a design for extensibility, concurrency and testability
Say it in 30 seconds
  • 45 minRound length: 5, 12, 16 and 12 minute blocks
  • 9 stepsRepeatable template, clarify through tests
  • 3 or 4 testsHappy path, boundary, failure, injected-clock case
Show what to say

“I run every LLD round the same way: clarify, use cases, entities, public interface, diagram, code the core, then stress the design for extensibility, concurrency and tests, in about 45 minutes. I pick a pattern only when a requirement hurts, name the race before I am asked, and inject the clock so tests are deterministic.”

Trade-offs

  • Pattern first vs pain first: start from the wording of the requirement and let the pain choose; a plain class is right when nothing hurts.
  • Lock per room vs one global lock: lock the invariant, not the world; hold it briefly, never across slow I/O.
  • In-memory lock vs database constraint: a lock protects one process; across servers a unique constraint or conditional update arbitrates.

Bring it up before they ask

Name the check-then-act race before they ask, and never say Node is single threaded: any await between the check and the act reopens the gap.

0 of 0 sections ticked
30-second answer

Run every round the same way: clarify, use cases, entities, interface, diagram, code the core, then stress the design, in about 45 minutes. Add a pattern only when a requirement hurts, and name the race before you are asked.

What interviewers score

An LLD round is not a pattern quiz. The rubric is about how you work.

What they scoreWhat it looks likeWhat loses points
ScopeQuestions firstCoding from sentence one
ModellingOne job per classA god class, or a class per noun
Working codeOne use case end to endHalf of everything, nothing runs
ExtensibilityA new requirement adds a classEditing a switch in four files

A 45-minute plan

The split is 5, 12, 16 and 12 minutes. The longest block is code, because only working code proves anything.

Behind schedule? Cut scope out loud: "I will code only book and cancel."

A 45-minute LLD round0 min9 min18 min27 min36 min45 minClarifyQuestions, use cases: 0–5 minModelEntities, interface: 5–12 minClass diagram: 12–17 minCodeThe core, one use case end to end: 17–33 minExtendOne new requirement: 33–38 minConcurrencyRaces, locks: 38–42 minTestsTests, summary: 42–45 minUse cases agreedDesign agreedCore worksDone

5 minutes to clarify and agree use cases, 12 to settle entities, the public interface and a class diagram, 16 to code the core, then 12 to stress the design: extensibility, concurrency and tests. Each marker is a checkpoint: if you are behind, say so and cut scope.

Text description
  • Clarify: Questions, use cases (0–5 min)
  • Model: Entities, interface (5–12 min), Class diagram (12–17 min)
  • Code: The core, one use case end to end (17–33 min)
  • Extend: One new requirement (33–38 min)
  • Concurrency: Races, locks (38–42 min)
  • Tests: Tests, summary (42–45 min)

The repeatable template

  1. Clarify

    Three to five questions: scope, size, failures.

  2. Use cases

    The user's verbs, as method names.

  3. Entities

    Nouns become classes, one job each. See the first lesson.

  4. Public interface

    Four to six signatures, and what each returns on failure.

  5. Class diagram

    Boxes and arrows. Five minutes.

  6. Code the core

    One use case, end to end.

  7. Extensibility check

    Aim to add a class, change none.

  8. Concurrency

    Find the check-then-act and make it one step.

  9. Tests

    Happy path, boundary, failure, injected clock.

Which pattern does this requirement call for?

Start from the wording of the requirement, not the pattern list.

No yes along the way? Plain classes are right.

Which pattern does this requirement call for?yesyesyesyesnoyesyesnononononoCommandObserverStateDecoratorStrategyDo the rules layer, eachone adjusting the resultof the one inside it?FactoryPlain classesDoes the class to createdepend on a string or atype that arrives asIs one job done inseveral interchangeableways, such as a price orDoes it behavedifferently depending onwhat phase it is in?Must other objects reactwhen this one changes,without it knowing them?Must an action beundone, queued, retriedor logged as a thing?

Must an action be undone, queued, retried or logged as a thing?

The extensibility check

The interviewer will add a requirement, so do it to yourself first: if they add a new X, how many existing classes do I edit?

  • A new vehicle type, payment method or pricing rule should be one new class and one registration line.
  • A new if in four files means a missing seam. See the SOLID lesson.
  • Say it as a sentence: "To add reservations I add a ReservationPolicy, and ParkingLot does not change."

Practise on the parking lot exercise: after each step, name a new requirement and the class you would add.

The concurrency checklist

Every round ends with "what if two people do this at once?"

  1. Find the check-then-act

    "If it is free, take it" is a race once two callers interleave.

  2. Make the pair one step

    A lock, claim-first, or one atomic operation.

  3. Lock the invariant, not the world

    One lock per room, held briefly.

  4. Make retries safe

    Give each request an id, so a repeat returns the first result. That is .

  5. Say what changes across servers

    A memory lock covers one process. Across servers the database arbitrates.

The racy version lets both callers through. The fix claims the room in the same step as the check. "Node is single threaded, so there is no race" loses points: any await reopens the gap.

const pause = () => new Promise((resolve) => setTimeout(resolve, 0)); // stands in for a payment or database call

// Check, then act, with a gap between them. Another caller can slip into the gap.
export class RoomsRacy {
  #owner = new Map();

  async book(room, who) {
    if (this.#owner.has(room)) return false; // check
    await pause();                           // anyone else may pass the same check now
    this.#owner.set(room, who);              // act: too late, the check is stale
    return true;
  }

  owner(room) { return this.#owner.get(room); }
}

Test with an injected clock

A test that sleeps for ten minutes never gets run. Pass in anything that reads the time, a random number or an id, so the test controls it.

The clock is a function and the test moves a variable. A hold made at time 0 is still held at 599,999 ms and gone at 600,000. Run the cell and move the clock yourself.

// The clock is a parameter, so a test can move time instead of waiting for it.
export class RoomHolds {
  #until = new Map();
  #now;
  #holdMs;

  constructor(now = Date.now, holdMs = 10 * 60 * 1000) {
    this.#now = now;
    this.#holdMs = holdMs;
  }

  isHeld(room) {
    return (this.#until.get(room) ?? 0) > this.#now();
  }

  // A hold lasts until the exact instant start + holdMs; at that instant it is gone.
  hold(room) {
    if (this.isHeld(room)) return false;
    this.#until.set(room, this.#now() + this.#holdMs);
    return true;
  }
}
try-it.js
// The clock is a variable you control. Move it and watch the hold expire.
let t = 0; // milliseconds
const holds = new RoomHolds(() => t); // the default hold lasts 10 minutes

console.log(holds.hold('A'));   // true
t = 599_999;
console.log(holds.hold('A'));   // false: one millisecond left
t = 600_000;
console.log(holds.hold('A'));   // true: the hold expired, so a new one starts
console.log(holds.isHeld('A')); // true

Worked example: a meeting-room booker

The prompt: "Design a system to book meeting rooms." The whole round, without the full code.

  1. Clarify

    Fixed rooms, one process, whole minutes. Out of scope: recurring meetings and calendar sync, named as follow-ups.

  2. Use cases

    book(room, start, end, who), cancel(bookingId), freeRooms(start, end).

  3. Entities

    Room, Booking, and RoomBooker as the one class that owns the rules.

  4. Public interface

    A clash returns false. A backwards slot or unknown room throws. Say that aloud: it is the contract.

  5. Class diagram

    RoomBooker owns rooms, and each room owns its bookings.

  6. Code the core

    The subtle part is the overlap test. Use half-open intervals (start in, end out), so a meeting ending at 10 does not clash with one starting at 10.

  7. Extensibility

    Recurring: a rule that expands into bookings. Notify: an observer. Best room: a strategy. Undo: a command.

  8. Concurrency and tests

    Two people booking one slot is the check-then-act again: check and claim in one step, with a lock per room. Across servers, a database constraint arbitrates. Test the boundary, a clash, a backwards slot, and inject the clock.

The core is book and freeRooms, short enough for a whiteboard.

// Half-open intervals [start, end): a meeting that ends at 10 does not clash with one that starts at 10.
export const overlaps = (a, b) => a.start < b.end && b.start < a.end;

export class RoomBooker {
  #bookings = new Map(); // room -> list of { start, end, who }

  constructor(rooms) {
    for (const room of rooms) this.#bookings.set(room, []);
  }

  isFree(room, start, end) {
    const list = this.#bookings.get(room);
    if (!list) throw new Error(`unknown room: ${room}`);
    return !list.some((b) => overlaps(b, { start, end }));
  }

  book(room, start, end, who) {
    if (!(start < end)) throw new RangeError('start must be before end');
    if (!this.isFree(room, start, end)) return false; // check and act in one step: no await between them
    this.#bookings.get(room).push({ start, end, who });
    return true;
  }

  freeRooms(start, end) {
    return [...this.#bookings.keys()].filter((room) => this.isFree(room, start, end));
  }
}

When you are stuck

Everyone stalls. The recovery is mechanical:

  • Say what you are stuck on. Silence reads as lost.
  • Go back to the use cases. Which verb am I implementing?
  • Write the simplest version: one class, a list, a loop.
  • Cut scope on purpose, and say where it would plug in.
  • Offer a choice, not a blank: "a lock per room or one global lock?"

Practise in this order

Work on a timer, out loud.

  1. Tic-tac-toe: entities and responsibilities.
  2. Parking lot exercise, then the question: SOLID in practice.
  3. Rate limiter and pricing rules: swappable, stackable behaviour.
  4. Vending machine and elevator: state machines.
  5. Event bus and text editor: observers, commands.

Follow-up questions

You are 20 minutes in and the design has grown too big. What do you do?

Stop adding. Finish the one use case that matters, and list the rest as extensions.

Your in-memory design must now run on several servers. What changes?

Memory locks protect nothing. Use a unique constraint or conditional update, and request ids for retries.

Check yourself

Question 1 of 3

Score 2 out of 3 or better to mark this page complete.

1. At minute 30 your parking lot works end to end. The interviewer asks: 'What if two gates call park() at the same moment?' What is the strongest answer?