cswork / libraryOpen practice workspace

Articles

LLD Interview: What Low-Level Design Is and How to Crack It

LLD means low-level design: class-level design of a component — entities, relationships, APIs, patterns. How it differs from HLD and how to crack it.

LLD (low-level design) is the class-level design of a single component: the entities, their relationships, the public APIs, and the design patterns that connect them. Where high-level design draws boxes and arrows between services, an LLD interview hands you one box — a parking lot, an elevator controller, a job scheduler — and asks you to specify what lives inside it, precisely enough that another engineer could implement it. Some companies call this round "machine coding" or "object-oriented design"; it is the same skill.

There is no hidden test suite and no single correct answer. The interviewer is grading your design judgment: do you pick the right abstractions, keep responsibilities separated, and write code that absorbs the inevitable follow-up ("now add electric vehicles") without a rewrite?

This guide covers how LLD differs from HLD, the concepts interviewers actually grade, a repeatable five-step flow, and one fully worked problem with running code — grounded in questions companies have really asked.

Key Takeaways

  • LLD designs the inside of one component — classes, interfaces, method signatures — while HLD decides which components exist. Interviewers test them in separate rounds because they are close to disjoint skills.
  • The hidden grading axis is change: adding a new vehicle type or rate plan should mean adding a class, not editing five existing ones. Find the axis of change and put it behind an interface.
  • Run the same five-step flow every time: clarify scope, name entities, define interfaces, write the two methods that carry the logic, then trace and extend.
  • Not every LLD prompt is pure object modeling. Some ask for public APIs, data structures with complexities, and an explicit state machine — read the prompt before reaching for a class diagram.
  • A compiling core beats fifteen stub classes. Interviewers notice when method signatures do not line up.

Video: How I Mastered Low Level Design Interviews

LLD vs HLD: two different rounds, two different skills

People conflate these because both have "design" in the name. They test different things, and preparing for one does little for the other.

High-Level Design (HLD)Low-Level Design (LLD)
Unit of designServices, databases, queues, cachesClasses, interfaces, methods
Typical prompt"Design Twitter's feed""Design the Tweet and Timeline classes"
OutputBox-and-arrow architecture diagramUML-ish class diagram + actual code
Core concernsScalability, latency, consistency, partitioningSOLID, design patterns, encapsulation, testability
You discussSharding, replication, CAP trade-offsInheritance vs composition, interface boundaries
DeliverableCapacity estimates, data flowCompilable code with the key methods filled in

A useful mental model: HLD decides you need a PaymentService; LLD decides what classes live inside that service and how Payment, Refund, and PaymentGateway talk to each other. Senior loops often include both rounds — if yours does, our system design prep guide covers the HLD half.

The parking-lot-style question is squarely LLD. The interviewer rarely cares whether your lot scales to a million spots; they care whether adding a new vehicle type forces you to edit five existing classes.

The five concepts interviewers are actually grading

LLD rewards a small, concrete vocabulary. You do not need every Gang-of-Four pattern memorized, but you must apply these fluently. (If the four OOP pillars themselves feel shaky, fix that first with our OOPs concepts guide — LLD rounds assume them.)

Encapsulation and clear responsibilities. Each class should own one reason to change. A ParkingSpot knows whether it is free and what fits in it; it does not compute fees. When a class starts doing two unrelated things, split it.

The SOLID principles, in priority order for interviews:

  • S — Single Responsibility: one class, one job. The most common failure is a god-class ParkingLot that parks cars, prices tickets, and prints receipts.
  • O — Open/Closed: open for extension, closed for modification. Adding an ElectricCar or a new pricing scheme should mean adding a class, not editing a switch statement scattered across the codebase.
  • L — Liskov Substitution: any subclass must be usable wherever its base type is expected, without surprises.
  • I — Interface Segregation: prefer small, focused interfaces over one fat one.
  • D — Dependency Inversion: depend on abstractions (FeeStrategy), not concrete classes (FlatHourlyFee). This is what lets you swap behavior at construction time.

Composition over inheritance. Reach for inheritance only for true "is-a" relationships. Prefer holding a collaborator (ParkingLot has a FeeStrategy) over deep class hierarchies, which are brittle and hard to test.

A few load-bearing design patterns. You will reuse the same handful constantly:

PatternWhen it shows up in LLD rounds
StrategySwappable algorithms: pricing rules, rate-limiting algorithms, elevator scheduling policies
FactoryCentralizing object creation: building the right Vehicle or Notification subtype
SingletonA single shared resource: one ParkingLot or one config registry (use sparingly — it hides dependencies)
ObserverReacting to events: notify displays when a spot frees up; alert subscribers on price change
StateAn object whose behavior changes by mode: an elevator door in Open / Closing / Closed / Faulted

Testability. If you cannot instantiate a class in isolation and assert on its behavior, the design is too coupled. Dependency injection (passing collaborators in via the constructor) is the cheap fix, and it signals seniority.

For a deeper treatment of UML notation and the standard question catalog, see our object-oriented design interview guide — it is the companion piece to this page.

The interview format: a repeatable five-step flow

Almost every LLD round follows the same arc. Run it explicitly and narrate as you go.

1. Clarify requirements and scope (3–5 min). Pin down what is in. For a parking lot: How many vehicle types? Multiple floors? Is pricing flat or tiered by spot size? Do we handle payments, or just entry/exit? Resist the urge to build everything — agree on a v1 scope out loud. This step alone separates strong candidates; weak ones start coding immediately and design the wrong system.

2. Identify the core entities and relationships (5 min). Name the nouns, then decide which ones deserve to be classes.

Asked at Google — Design an elevator control system A building has N floors and M elevators. Riders press Up or Down from a floor (hall calls) or pick a destination inside a car (car calls). Each car reports its door state, load, position, and fault signals. Model the core classes: Elevator, Door, Floor, Request, Controller, Scheduler.

Notice what Google's prompt does: it separates HallCall from CarCall even though both are "requests." That distinction is the entity-modeling test. A hall call carries a direction but no destination and can be served by any car; a car call belongs to exactly one elevator. Candidates who collapse them into one Request class spend the rest of the interview fighting their own model. Extract the nouns, but interrogate whether two similar-sounding nouns really have the same shape and lifecycle.

3. Define interfaces and apply SOLID + patterns (5 min). Decide where polymorphism lives. Vehicles differ by type, so Vehicle is an abstract base. Pricing varies independently, so it becomes a FeeStrategy interface (Strategy pattern) injected into the lot. State which principle each decision serves — "I'm putting pricing behind an interface so a new rate plan satisfies Open/Closed."

4. Write the code (15–20 min). Implement the core classes and the one or two methods that carry the system's logic (here: park and unpark). You do not need every getter. Make it compile and run in your head.

5. Walk through scenarios and discuss extensions (5 min). Trace a car parking and leaving. Then handle the follow-ups: "How would you add electric-vehicle charging spots?" or "Make it thread-safe." Show your design absorbs change without surgery.

Worked example: design a parking lot

The parking lot is the canonical LLD question, and companies still ask it by name.

Asked at Uber — Design an object-oriented parking lot system Model a lot with multiple levels, where each spot is small, medium, or large. Motorcycles, cars, and buses each need a compatible spot size, and a bus may need several adjacent large spots. Support vehicle entry with spot reservation, exit, and payment.

Here is a clean single-floor v1 and code that runs. (For the full multi-level treatment — pricing models, and the two-gate race condition that guide covers — see our dedicated parking lot design guide.)

Scope we agreed on: three vehicle types (motorcycle, car, truck); three spot sizes with fit rules; flat hourly pricing with a one-hour minimum; single floor; single entry/exit. We use the Strategy pattern for pricing and the abstract base + enum combo for vehicles and spots so new types are additive.

Entities and relationships

ParkingLot ──has many──> ParkingSpot ──holds──> Vehicle (abstract)
    │                                             ├─ Motorcycle
    ├──uses (Strategy)──> FeeStrategy             ├─ Car
    │                       └─ FlatHourlyFee      └─ Truck
    └──issues──> Ticket ──references──> Vehicle, ParkingSpot

The code

from abc import ABC, abstractmethod
from datetime import datetime
from enum import Enum

class VehicleType(Enum):
    MOTORCYCLE = 1
    CAR = 2
    TRUCK = 3

class Vehicle(ABC):
    def __init__(self, plate: str, vtype: VehicleType):
        self.plate = plate
        self.vtype = vtype

class Motorcycle(Vehicle):
    def __init__(self, plate): super().__init__(plate, VehicleType.MOTORCYCLE)

class Car(Vehicle):
    def __init__(self, plate): super().__init__(plate, VehicleType.CAR)

class Truck(Vehicle):
    def __init__(self, plate): super().__init__(plate, VehicleType.TRUCK)

class SpotType(Enum):
    SMALL = 1
    MEDIUM = 2
    LARGE = 3

# Fit rules: a motorcycle fits anywhere; a car needs MEDIUM+; a truck needs LARGE.
FIT = {
    VehicleType.MOTORCYCLE: {SpotType.SMALL, SpotType.MEDIUM, SpotType.LARGE},
    VehicleType.CAR: {SpotType.MEDIUM, SpotType.LARGE},
    VehicleType.TRUCK: {SpotType.LARGE},
}

class ParkingSpot:
    def __init__(self, spot_id: str, spot_type: SpotType):
        self.spot_id = spot_id
        self.spot_type = spot_type
        self.vehicle = None

    def is_free(self) -> bool:
        return self.vehicle is None

    def can_fit(self, vehicle: Vehicle) -> bool:
        return self.spot_type in FIT[vehicle.vtype]

    def park(self, vehicle: Vehicle):
        self.vehicle = vehicle

    def vacate(self):
        self.vehicle = None

class Ticket:
    def __init__(self, ticket_id: str, vehicle: Vehicle, spot: ParkingSpot):
        self.ticket_id = ticket_id
        self.vehicle = vehicle
        self.spot = spot
        self.issued_at = datetime.now()

# --- Strategy pattern: pricing varies independently of the lot ---
class FeeStrategy(ABC):
    @abstractmethod
    def compute(self, ticket: Ticket, exit_time: datetime) -> float: ...

class FlatHourlyFee(FeeStrategy):
    def __init__(self, rate_per_hour: float):
        self.rate = rate_per_hour

    def compute(self, ticket: Ticket, exit_time: datetime) -> float:
        seconds = (exit_time - ticket.issued_at).total_seconds()
        hours = max(1, -(-int(seconds) // 3600))  # ceil, minimum 1 hour
        return hours * self.rate

class ParkingLot:
    def __init__(self, spots, fee_strategy: FeeStrategy):
        self.spots = spots                # composition: lot HAS spots
        self.fee_strategy = fee_strategy  # dependency injection
        self.tickets = {}
        self._counter = 0

    def _find_spot(self, vehicle: Vehicle):
        for spot in self.spots:
            if spot.is_free() and spot.can_fit(vehicle):
                return spot
        return None

    def park(self, vehicle: Vehicle) -> Ticket:
        spot = self._find_spot(vehicle)
        if spot is None:
            raise RuntimeError("Lot full for this vehicle type")
        spot.park(vehicle)
        self._counter += 1
        ticket = Ticket(f"T{self._counter}", vehicle, spot)
        self.tickets[ticket.ticket_id] = ticket
        return ticket

    def unpark(self, ticket_id: str, exit_time: datetime) -> float:
        ticket = self.tickets.pop(ticket_id)
        fee = self.fee_strategy.compute(ticket, exit_time)
        ticket.spot.vacate()
        return fee

Why this design scores well

  • Single Responsibility: ParkingSpot tracks occupancy, FeeStrategy prices, ParkingLot coordinates. No god-class.
  • Open/Closed: a new ElectricCar is one subclass plus one entry in FIT. A "weekend surge" price is a new FeeStrategy — zero edits to ParkingLot.
  • Dependency Inversion: ParkingLot depends on the FeeStrategy abstraction, so pricing is swapped at construction:
lot = ParkingLot(spots, FlatHourlyFee(2.0))
# later, no change to ParkingLot:
# lot = ParkingLot(spots, WeekendSurgeFee(2.0, multiplier=1.5))
  • Testable: every class is constructible in isolation. You can unit-test FlatHourlyFee.compute without a lot, or assert that a truck never lands in a small spot.

A quick trace: park a Car, the lot scans for the first free MEDIUM-or-LARGE spot, occupies it, returns ticket T1. On exit after 90 minutes, FlatHourlyFee ceilings to 2 hours and bills 2 × $2.00 = $4.00; the spot vacates and is reusable.

When LLD means APIs and state machines, not just classes

A growing share of LLD prompts, especially at backend-heavy teams, grade something beyond object modeling: public API signatures, data structures with stated complexities, and explicit state machines. The word "LLD" in the prompt title is your cue.

Asked at Amazon — Design delayed job scheduler (LLD) Build a service that runs a job after a caller-chosen delay, accurate to the second. The prompt names its own grading rubric: the public API surface, the in-memory and on-disk structures with their costs, a schema that survives restarts, a state machine for job status, and a protocol for leasing work to executors.

For a prompt like this, a class diagram alone fails. The interviewer wants you to say: schedule(job, delay) -> job_id and cancel(job_id) -> bool are the public API; a min-heap keyed on execute_at gives O(log n) insert and O(log n) pop-min for the in-memory tier; a jobs table with a status column (SCHEDULED → LEASED → RUNNING → DONE | FAILED) makes execution crash-safe; and a timed lease keeps a second worker from grabbing a job whose owner is still alive. Pair the lease with idempotent execution, because a lease can expire under a worker that is merely slow, and both may run. The class design still matters, but it serves the API contract rather than replacing it.

That job-status state machine is worth drawing, because the lease-expiry edge is the part candidates forget:

low level design interview what lld is and how to crack it

If your loop leans this direction, our API design interview framework covers the signature-and-contract half in depth.

How to answer this in the interview

Narrate your reasoning — the interviewer is grading your process, not just the final code.

  • Lead with scope, not classes. Your first sentence should be a clarifying question. "Single floor or multi-floor? Do we price by time, spot size, or both?" Silence-then-coding reads as junior.
  • State the trade-off when you make a choice. "I'll model pricing as a Strategy rather than an if vehicle.type == chain, so new rate plans don't touch existing code." Naming the principle (Open/Closed) shows intent.
  • Prefer composition; justify any inheritance. Use an abstract Vehicle because the subtypes genuinely are vehicles. Do not subclass ParkingLot to make a PaidParkingLot — inject behavior instead.
  • Write less, but make it real. A compiling core (entities + the two methods that carry logic) beats fifteen half-finished stub classes. Leave getters implied.
  • Call out what you are deferring. "I'm skipping concurrency for v1 but I'd guard park with a lock or a per-spot CAS." This shows you see the gap and chose to defer it deliberately.

The anti-patterns that sink candidates: a single 200-line god-class, deep inheritance trees, hardcoded if/elif chains where polymorphism belongs, and code that would not actually compile.

Common follow-up questions

Interviewers extend the base problem to probe depth, and the follow-ups are often more scripted than the opening prompt. The elevator family shows how specific they get.

Asked at Remitly — Design an Elevator System and Scheduler Define the core classes for the cars, controller, requests, floors, buttons, and sensors, covering fault handling and concurrent access, then propose a scheduling policy that weighs travel time against fairness. The kicker: with pending stops at floors 1, 99, and 2 and only one internal queue structure allowed, prove the car visits 2 before 99, and spell out the queue operations, tie-breaking rules, and their costs.

That [1, 99, 2] probe is testing whether you know the SCAN idea: serve all stops in the current direction before reversing, which a plain FIFO queue gets wrong. Two sorted sets (stops above and below the current floor) or one ordered structure with direction-aware iteration answers it cleanly.

Follow-ups you should have an answer ready for:

  • "Make it thread-safe." Concurrent park calls can hand the same spot to two cars. Discuss a per-lot lock (simple, low throughput) versus per-spot atomic compare-and-set on the is_free → occupied transition. Mention that _find_spot + park must be one atomic step. Our concurrency interview guide covers the failure modes interviewers fish for here.
  • "Add a new vehicle type / spot size." Walk them through it live: one new enum value, one new Vehicle subclass, one line in FIT. The fact that nothing else changes is the point.
  • "Support multiple floors." Introduce a ParkingFloor owning spots; ParkingLot aggregates floors and delegates the search. A spot id becomes floor.spot.
  • "Price by spot size, not a flat rate." A new FeeStrategy — no change to ParkingLot. This is the payoff of the Strategy pattern; say so.
  • "How would you find the nearest available spot?" Move from a linear scan to a priority queue of free spots keyed by distance to entrance, updated on park/vacate.
  • "How do you test this?" Name concrete cases: truck rejected when only small spots remain, fee ceiling at the hour boundary, double-unpark of one ticket, lot-full behavior.

FAQ

What does LLD stand for in interviews?

LLD stands for low-level design: the class-level design of a single component, covering its entities, relationships, public APIs, and the design patterns that connect them. It is the counterpart to HLD (high-level design), which decides which services exist and how they communicate.

Is LLD the same as object-oriented design or machine coding?

Mostly, yes. "Object-oriented design," "machine coding," and "LLD" all name the round where you turn a small system into classes and working code. "Machine coding," the common name at Indian product companies, is often a longer round where the code must actually run. Some LLD prompts also go beyond pure OOD into API signatures and state machines.

Do I need to write compilable code in an LLD round?

Usually, yes — at minimum the core entities and the one or two methods that carry the logic. Unlike HLD, where a diagram is the deliverable, LLD interviewers check that method signatures line up and the happy path traces correctly. Pseudocode is acceptable only if the interviewer says so.

Which design patterns should I know for LLD interviews?

Five patterns cover most rounds: Strategy (swappable algorithms like pricing), Factory (centralized object creation), Observer (event notification), State (mode-dependent behavior like an elevator door), and Singleton (shared resources, used sparingly). Knowing when not to apply a pattern matters as much as knowing the pattern.

How is LLD different from system design?

"System design" usually means HLD: services, databases, queues, and the trade-offs between them, such as sharding, replication, and consistency. LLD zooms into one service and designs its internals as classes and interfaces. HLD answers which boxes exist and how they talk; LLD answers what is inside one box.

What are the most common LLD interview questions?

Parking lot, elevator control system, vending machine, rate limiter, chess or tic-tac-toe, a Splitwise-style expense tracker, and a job scheduler. Companies reuse this catalog because each one has a clear axis of change to probe with follow-ups.