Home › Low-Level Design › How to Approach an LLD Interview
H

How to Approach an LLD Interview

The LLD interview is not a coding puzzle. It is a conversation about how you turn a fuzzy request into classes that a teammate could actually build. I have run close to 150 of these loops across FAANG-tier teams and senior-heavy startups, and the people who pass are rarely the ones who memorized the most patterns. They are the ones who stay calm, carve the problem into pieces, and keep talking while they draw. In a 45-minute round the bar is plain: by minute 35 I want a class diagram, two or three key methods in real code, and one traced flow.

Low-Level DesignMedium10 min read

If you remember five things

This is a method, not a template. Everything below is the defense of these five moves.

  1. Scope before you type. Spend the first five minutes on nouns, verbs, and what you are leaving out. A wrong first model costs you ten minutes you never get back.
  2. Nouns become classes, verbs become methods. The hard part is the relationships between them. Get ownership right and the design almost writes itself.
  3. Put behavior next to data. A `ParkingSpot` that cannot tell you whether a vehicle `fits` should not exist. Tell, Don't Ask beats anemic data bags.
  4. A pattern must name the change it absorbs. "Pricing will change, so pricing is a Strategy" is good. "I added a Factory because factories are good" fails you.
  5. Name concurrency even if you do not solve it. "Two threads could grab the same spot here, so I would guard this." At senior and staff level this one line is often the deciding signal.

1The problem it solves#

The trap is the open-ended prompt. The interviewer hands you "Design a parking lot" and stops. No constraints, no class list, no hint about how far to go.

Most candidates do one of two bad things:

  • Start typing class ParkingLot { within thirty seconds, before they know what they are building.
  • Freeze, because the problem feels enormous and the whiteboard is blank.

Both lose points, and in a timed round both are expensive.

What the round actually tests is a single question: can you take ambiguity and produce a design that is correct, extensible, and small enough to explain in 45 minutes? You are graded on judgment, not typing speed:

  • Can you pick the right objects?
  • Do you know when to stop adding features?
  • Can you defend a decision when I push back with "what if pricing changes next quarter"?

A good approach kills the panic. It gives you a sequence to follow so you are never staring at a blank board wondering what comes next. I treat the first five minutes as pure scoping and I say so out loud, because that one sentence buys me room to think.

From the interviewer's chair

I start scoring before you draw a single box. The candidate who says "let me pin down scope first, I am leaving payments out unless you want them" has already told me they have done this for real. The one who opens a code editor and types a class name has told me the opposite. Narrate the plan, then execute it.

2The idea (mental model)#

Think of LLD as four passes over the same problem, each one narrower than the last.

  1. Scope. Agree on what you are building and what you are ignoring. Nouns and verbs. Budget five minutes.
  2. Model. Turn the nouns into classes and the verbs into methods. Draw relationships. Budget ten.
  3. Behavior. Walk one real flow end to end. Make the objects talk. Budget ten to fifteen.
  4. Stress. Poke at your own design. Concurrency, a new feature, an edge case. Show you see the cracks. Whatever time is left.

That is the whole thing. Every strong interview I have seen follows this shape, even when the candidate never named it.

The mental hook:

  • Nouns become classes.
  • Verbs become methods.
  • The hard part, and where points are won or lost, is the relationships between them.

The one principle under all of it is the Single Responsibility Principle: one class, one reason to change. If I cannot say in one sentence what a class is responsible for, it is doing too much.

Why the four passes matter

They turn a scary open prompt into a checklist you can run on autopilot. You stop improvising and start executing a known sequence, which is exactly what keeps you calm and talking. The interviewer reads that composure as experience, because it is.

3Structure#

Make it concrete with a parking lot, the classic warm-up. Say I have scoped it to: multiple floors, three vehicle sizes, a ticket on entry, pay on exit, assign the nearest free spot that fits.

  • Nouns jump out: ParkingLot, Floor, ParkingSpot, Vehicle, Ticket.
  • Verbs: park, unpark, calculate fee.
  • Relationships are where candidates get sloppy, so slow down here.

The relationships, said out loud as I draw:

  • A ParkingLot has floors, and those floors die with the lot: that is composition.
  • A Floor contains spots: composition again.
  • A Spot references a Vehicle when occupied but does not own it: a loose association.
  • Car and Truck are kinds of Vehicle: inheritance.

Say the words while you draw. "The lot owns its floors. A spot just points at whatever vehicle is sitting in it." That running commentary is half your score. The distinction between composition (filled diamond, owned lifecycle) and association (plain arrow, borrowed reference) is exactly the judgment I am listening for.

classDiagram class ParkingLot { -List~Floor~ floors +park(vehicle) Ticket +unpark(ticket) double } class Floor { -int level -List~ParkingSpot~ spots +findSpot(size) ParkingSpot } class ParkingSpot { -SpotSize size -boolean free +assign(vehicle) void +release() void } class Vehicle { -String plate -SpotSize size } class Car class Truck class Ticket { -String id -long entryTime } ParkingLot *-- Floor Floor *-- ParkingSpot Vehicle <|-- Car Vehicle <|-- Truck ParkingSpot --> Vehicle ParkingLot --> Ticket
The class model at a glance. Notice the two arrow shapes: the filled diamonds (ParkingLot to Floor, Floor to ParkingSpot) say "delete the parent and the children go with it," while the plain arrow from ParkingSpot to Vehicle says "the spot forgets the vehicle the moment it leaves." Those shapes are the ownership rules made visible, and they give you and the interviewer something concrete to point at.

Why the diagram earns its keep:

  • It makes the ownership rules visible instead of implied.
  • It gives both of you something to point at while you talk.
  • I draw this by hand in a real interview: boxes and arrows, no fancy tooling.

4Implementation in Java#

Now turn the diagram into code. I do not write every getter. I write the parts that show the design decisions: how a spot gets chosen, how the fee is computed, where the strategy lives.

enum SpotSize { SMALL, MEDIUM, LARGE }

abstract class Vehicle {
    final String plate;
    final SpotSize size;
    Vehicle(String plate, SpotSize size) {
        this.plate = plate;
        this.size = size;
    }
}

class Car extends Vehicle {
    Car(String plate) { super(plate, SpotSize.MEDIUM); }
}

class Truck extends Vehicle {
    Truck(String plate) { super(plate, SpotSize.LARGE); }
}

class ParkingSpot {
    final SpotSize size;
    private Vehicle current;

    ParkingSpot(SpotSize size) { this.size = size; }

    boolean fits(Vehicle v) { return current == null && v.size.ordinal() <= size.ordinal(); }

    void assign(Vehicle v) { this.current = v; }
    void release() { this.current = null; }
}

class Floor {
    final int level;
    private final List<ParkingSpot> spots;

    Floor(int level, List<ParkingSpot> spots) {
        this.level = level;
        this.spots = spots;
    }

    Optional<ParkingSpot> findSpot(Vehicle v) {
        return spots.stream().filter(s -> s.fits(v)).findFirst();
    }
}

class Ticket {
    final String id;
    final long entryTime;
    final ParkingSpot spot;

    Ticket(String id, ParkingSpot spot) {
        this.id = id;
        this.entryTime = System.currentTimeMillis();
        this.spot = spot;
    }
}

interface FeeStrategy {
    double fee(long entryTime, long exitTime);
}

class HourlyFee implements FeeStrategy {
    public double fee(long entryTime, long exitTime) {
        long hours = Math.max(1, (exitTime - entryTime) / 3_600_000);
        return hours * 40.0;
    }
}

class ParkingLot {
    private final List<Floor> floors;
    private final FeeStrategy feeStrategy;

    ParkingLot(List<Floor> floors, FeeStrategy feeStrategy) {
        this.floors = floors;
        this.feeStrategy = feeStrategy;
    }

    synchronized Ticket park(Vehicle v) {
        for (Floor f : floors) {
            Optional<ParkingSpot> spot = f.findSpot(v);
            if (spot.isPresent()) {
                spot.get().assign(v);
                return new Ticket(UUID.randomUUID().toString(), spot.get());
            }
        }
        throw new IllegalStateException("lot full");
    }

    synchronized double unpark(Ticket t) {
        double fee = feeStrategy.fee(t.entryTime, System.currentTimeMillis());
        t.spot.release();
        return fee;
    }
}

Three things I would point out to the interviewer as I write this:

  • FeeStrategy is the Strategy pattern, an interface because pricing always changes: flat rate today, surge pricing tomorrow, free first hour next quarter. I will not hardcode a pricing rule into the lot. I inject the strategy through the constructor, which is Dependency Inversion in practice: ParkingLot depends on the FeeStrategy abstraction, never on HourlyFee directly.
  • The fee math is deliberate: 3_600_000 milliseconds per hour, with a one-hour minimum via Math.max(1, ...) at 40.0 per hour, so a 10-minute stay still costs 40.
  • park and unpark are synchronized because two cars can hit the entrance at the same instant and I do not want to hand the same spot to both. That one word opens the concurrency conversation, which I want.
Red flag that fails you here

Writing park without synchronized and never mentioning it. If two threads both call findSpot before either calls assign, they get the same free spot and you have double-booked it. Saying nothing reads as not seeing it. Even better than the lock: flag its limit before I do. A single synchronized lock serializes the whole lot, so at real scale I would move to a lock per floor, or a lock-free scan with an atomic claim on the spot.

5A real-world example#

This exact shape shows up everywhere once you see it. Swap the nouns and the structure survives.

Cinema seat booking is the same skeleton.

  • Theater owns Screens, a screen has Seats, a Booking references the seats it holds.
  • "Hold a seat under concurrency" is identical to "hand out one parking spot."
  • The one twist: a seat hold is usually time-boxed, released after 5 minutes if payment does not land, which adds a little state the parking lot never needed.

An elevator reuses the strategy idea.

  • The lot had a FeeStrategy; the elevator has a SchedulingStrategy that decides which car answers a call: nearest-car today, energy-saving mode at night.
  • Same move: pull the decision that changes into its own interface.
  • It also leans on the State pattern, since a car behaves differently when idle, moving up, or moving down.

Even Splitwise fits.

  • Group owns members, an Expense references who paid and who owes.
  • The split logic (equal, exact, percentage) is a strategy.

The takeaway I give candidates: master two prompts, the parking lot and one stateful one like a vending machine, and you have covered roughly 80 percent of what gets asked. Internalize one well-modeled problem and you can reshape it into most of the others.

6When to use it, and when not to#

Use the four-pass approach for any LLD or object-oriented design round.

  • It scales from a forty-minute screen to a long onsite.
  • If the prompt is a single class or an algorithm with a clear signature, skip the ceremony and just code. This method is for open-ended design.

Do not over-apply the patterns. Strategy, factory, observer: these are tools, not trophies.

  • I have watched candidates bolt an AbstractParkingSpotFactoryBuilder onto a problem that needed three plain classes. That hurts you and burns minutes you will miss later.
  • Reach for a pattern only when you can name the change it absorbs. "Pricing will change, so pricing is a Strategy." Good. "I added a Factory because factories are good." Bad.
  • This is the YAGNI principle doing real work: you are not gonna need the abstraction until the second variation shows up.
Pause before you read on: a candidate wraps every class in a Factory and adds an Observer "for future events" on a parking lot that has no events yet. Strong design or weak?
Reveal the answer
Weak, and it actively costs them. Every abstraction they cannot justify is a maintenance tax and a minute spent not finishing the core model. A pattern with no named change it absorbs is decoration. The strong move is the opposite: the simplest thing that satisfies the agreed scope, with one clean seam (the Strategy) where I already know change is coming. A finished 5-class design beats an abandoned 15-class one every single loop.

When in doubt, build the simplest thing that satisfies the scope you agreed on, then let me ask for more. I would rather see a clean small design you can extend than a sprawling one you cannot finish.

7Common mistakes#

Coding before scoping. The number one failure. You design the wrong thing beautifully. Spend the first five minutes on nouns, verbs, and what is out of scope. "I am leaving payments out unless you want them" signals seniority on its own.

Anemic classes. Data bags with only getters and setters, and all the logic sitting in one giant service. If ParkingSpot cannot tell me whether a vehicle fits, why does it exist? Put behavior next to the data it touches. That is the Tell, Don't Ask principle.

God objects. One ParkingLotManager that does everything. Spread responsibility: a floor should find its own spot. A God object is a Single Responsibility violation wearing a cape.

Ignoring concurrency. Even if you do not fully solve it, name it. "Two threads could grab the same spot here, so I would guard this." Saying nothing reads as not seeing it, and at senior and staff level this is often the deciding signal.

Enum-driven type checks. Writing if (vehicle.type == CAR) all over the place instead of using polymorphism. If you are branching on a type field everywhere, your inheritance is in the wrong spot. It is also an Open-Closed violation: every new vehicle type forces you to edit every one of those branches.

Going silent. You stop narrating and just think. I cannot score silence. Think out loud, even the dead ends.

The interviewer scores what you say, not what you think. Silence is an unforced error.

8Where it shows up in interviews#

Almost every company that runs an LLD round reaches for one of a dozen classic prompts.

  • The usual suspects: parking lot, elevator, vending machine, cinema booking, Splitwise, rate limiter, a card game, a logging library.
  • Who asks: Amazon, Uber, and most senior and staff loops at India-based product companies run at least one.
  • At staff level the follow-ups get sharper, usually concurrency and extensibility.

A strong runtime flow usually seals it. After the class diagram, I love when a candidate walks me through one request. Here is the park flow I expect someone to narrate:

sequenceDiagram participant Driver participant Lot as ParkingLot participant Floor participant Spot as ParkingSpot Driver->>Lot: park(car) Lot->>Floor: findSpot(car) Floor->>Spot: fits(car)? Spot-->>Floor: true Floor-->>Lot: spot Lot->>Spot: assign(car) Lot-->>Driver: Ticket
One park call traced through the objects. Notice the control flows down the ownership chain (Lot asks Floor, Floor asks Spot) and the answer bubbles back up. The lot never reaches into a spot directly: it goes through the floor that owns it, which is the composition from the class diagram playing out at runtime.

When you can draw the classes, write the key methods, and then trace a single call through the objects, you have shown the whole loop. That is what gets a yes from me. Keep it simple, scope it first, and never stop talking.

Follow-ups the interviewer will ask (and your answers)
"Two cars reach the entrance at the same instant. What stops a double-booking?"
Today `park` is `synchronized`, so the find-and-assign is atomic and only one car gets the spot. That one lock serializes the whole lot, though, so at scale I would narrow it: a lock per floor, or a lock-free scan that does an atomic compare-and-set to claim the spot, retrying on contention. The invariant is that finding a free spot and marking it taken must be a single atomic step.
"Pricing changes next quarter to free-first-hour, then surge. How much rework?"
None in the lot. Pricing lives behind the `FeeStrategy` interface and is injected through the constructor, so I add a new implementation and wire it in. `ParkingLot` never names `HourlyFee`, so it does not change. That is the whole point of putting the Strategy there: it is the one seam where I already knew change was coming.
"Add electric-vehicle spots with charging. Where does that go?"
A new `SpotSize` or a capability flag on `ParkingSpot`, and `fits` grows to check it. The vehicle carries its need, the spot advertises its capability, and the match stays inside `fits` so no caller branches on a type field. If charging gets its own pricing, that rides the existing `FeeStrategy` rather than a new `if`.
"Why Strategy for the fee but not a Factory for the vehicles here?"
Because I can name the change Strategy absorbs: pricing rules vary and will keep varying. I cannot yet name a change a vehicle Factory absorbs. `new Car(plate)` is fine until creation logic actually gets complex or configurable. I add the Factory the day the second reason to centralize creation shows up, not before. YAGNI.
"How would you unit test the fee logic?"
`HourlyFee.fee` is a pure function of two timestamps, so it tests in isolation with no lot at all: pass entry and exit, assert the amount. I would cover the one-hour minimum (a 10-minute stay returns 40), an exact multiple of an hour, and a long stay. Pulling pricing into its own object is what makes it trivially testable, which is a side benefit of the Strategy seam.

What you can now explain in the room

  • The four-pass method (scope, model, behavior, stress) and roughly how many minutes each gets.
  • Why nouns become classes and verbs become methods, and why relationships are the hard part.
  • The difference between composition and association, and how to show it on a diagram with arrow shapes.
  • Where the Strategy pattern belongs and how injecting it gives you Dependency Inversion for free.
  • How to raise concurrency with `synchronized`, and why one lock on the whole lot does not scale.
  • Why a finished small design beats an abandoned large one, and how to resist pattern-stuffing with YAGNI.

Next, try the same method on a vending machine or an elevator, and read up on the Strategy and State patterns since they carry most LLD prompts.

Get a prep plan built around your How to Approach an LLD Interview interview

Tell us the role and your interview date. We will send back a dated, day by day plan. Free.

Thanks. Check your inbox, your How to Approach an LLD Interview prep plan is on the way.