Introduction: LLD Tests Modeling Real-World Systems in Code
When most engineers hear 'design interview,' they think of system design: distributed systems, load balancers, and database sharding at internet scale. But there is a second category of design interview that appears more frequently at non-FAANG companies and is routinely under-prepared for: low-level design, or LLD. LLD interviews ask you to model a real-world system as a set of classes, relationships, and behaviors using object-oriented principles — not to architect a distributed platform.
LLD interviews are common at companies like Atlassian, Goldman Sachs, Flipkart, Thoughtworks, Walmart Global Tech, and hundreds of mid-tier technology companies that care deeply about code quality and software craftsmanship. They appear in senior engineer loops where the interviewer wants evidence that you think in abstractions — that you can take a messy real-world system and translate it into clean, extensible, maintainable code.
Three problems dominate the LLD interview landscape: the parking lot, the elevator system, and the URL shortener. These three canonical problems have been asked thousands of times across the industry because each one tests a distinct set of OOP skills. The parking lot tests composition and inheritance. The elevator tests state machines and scheduling algorithms. The URL shortener tests encoding, expiry, and interface design. Together they form a complete picture of OOP modeling ability.
This guide walks through all three canonical problems, provides a reusable 5-step OOP design framework that applies to every LLD problem, and explains what interviewers are actually evaluating when they ask you to design a parking lot. If you have been preparing exclusively for system design interviews, this guide will reorient your preparation for the class of company where LLD fluency is the actual differentiator.
One important note on scope: LLD and system design are evaluated on different dimensions. Low-level design (LLD) interviews are distinct from system design — system design tests architecture at scale, LLD tests object-oriented modeling of a single system. A candidate who answers an LLD problem by discussing horizontal scaling and database replication has missed the question entirely.
What Is LLD / OOP Design? — Definition, Context, and What Interviewers Evaluate
Low-level design (LLD), also called object-oriented design (OOD), is the practice of translating a set of functional requirements for a single system into a concrete class structure: what classes exist, what attributes and methods each class has, how classes relate to each other through composition or inheritance, and how the system behaves under various inputs. The output of an LLD interview is not an architecture diagram — it is a UML-adjacent class model or working pseudocode/real code for the system.
LLD interviews appear most frequently in three interview contexts: (1) at product companies that value software craftsmanship and clean architecture, such as Atlassian, Thoughtworks, and mid-tier fintech companies, (2) as a component of senior engineer loops where behavioral and architectural thinking are both evaluated, and (3) in backend engineering roles where the daily work involves designing and maintaining large, stateful class hierarchies.
What does 'design a parking lot' actually test? It tests five things simultaneously: encapsulation (hiding internal state behind well-defined interfaces), inheritance (modeling the Vehicle hierarchy so ParkingLot doesn't care whether a vehicle is a Car or a Truck), polymorphism (treating all vehicles uniformly through a common interface), composition (ParkingLot contains ParkingFloors which contain ParkingSpots), and abstraction (the ParkingLot interface hides the complexity of floor selection and spot assignment). A candidate who cannot name and apply these principles during the interview signals that their object-oriented instincts are not yet interview-ready.
The most common failure mode in LLD interviews is jumping into code without clarifying requirements. Interviewers universally penalize candidates who start defining classes before they have established what the system actually needs to do. The second most common failure mode is over-engineering: designing for scale requirements that were never stated, adding unnecessary abstract factory patterns, or creating inheritance hierarchies three levels deep for a system that has two concrete types. LLD interviewers reward clear thinking and simple, extensible design — not architectural maximalism.
Preparation for LLD interviews is more pattern-driven than preparation for coding problems. Once you have internalized the 5-step OOP design framework and studied the three canonical problems, you can apply the same mental model to any LLD prompt you encounter: design a library management system, design an ATM, design a chess game, design a hotel booking system. The entities and behaviors change; the structural approach does not.
- LLD vs system design: system design tests architecture at scale, LLD tests OOP modeling of a single system
- LLD appears at: Atlassian, Goldman Sachs, Flipkart, Thoughtworks, Walmart Global Tech, mid-tier fintech
- Design a parking lot tests: encapsulation, inheritance, polymorphism, composition, and abstraction
- Output of an LLD interview: a class model or working pseudocode — not an architecture diagram
- Biggest failure modes: jumping to code without requirements, over-engineering with unused design patterns
The Parking Lot Problem — Requirements, Core Classes, and Edge Cases
The parking lot LLD problem is the most frequently asked OOP design interview question in the industry. It appears in this guide first because it is the cleanest illustration of all five core OOP principles operating together. Before writing a single class, clarify the requirements: How many floors? What vehicle types are supported (motorcycle, car, truck)? Are there dedicated spots per vehicle type? Is there a fee system? Can a vehicle park on any floor or is floor assignment managed? These clarifications determine your class structure.
The parking lot LLD problem tests five core OOP principles: encapsulation, inheritance, polymorphism, composition, and abstraction. Encapsulation: each ParkingSpot owns its own availability state, and no outside class sets that state directly. Inheritance: Motorcycle, Car, and Truck all extend a base Vehicle class, allowing ParkingSpot to accept a Vehicle reference without knowing the concrete type. Polymorphism: a ParkingFloor can call spot.fits(vehicle) on any spot and get the right answer regardless of spot type. Composition: ParkingLot contains a list of ParkingFloors, each of which contains a list of ParkingSpots. Abstraction: the ParkingLot.parkVehicle(vehicle) method hides floor selection, spot assignment, and ticket generation behind a single call.
The core classes for the parking lot system are: ParkingLot (entry point, manages floors, exposes park/unpark interface), ParkingFloor (contains spots of each type, exposes availability count), ParkingSpot (knows its type and current occupancy, issues and releases spots), Vehicle (abstract base class with type and license plate), concrete vehicles Motorcycle / Car / Truck extending Vehicle, and Ticket (issued on entry, used to calculate fee and release spot on exit). Relationships: ParkingLot has many ParkingFloors (composition), each ParkingFloor has many ParkingSpots (composition), each ParkingSpot holds zero or one Vehicle (association), each Ticket references one ParkingSpot and one Vehicle.
Edge cases that interviewers expect you to address: What happens when the lot is full? (ParkingLot.parkVehicle returns null or throws a FullLotException.) What happens if the same vehicle tries to park twice? (Check for existing ticket by license plate.) What happens if a Ticket is lost? (Ticket lookup by license plate as fallback.) What if a motorcycle can fit in a car spot when motorcycle spots are full? (Introduce a spot-fitting policy that allows overflow with a different fee rate.) Addressing edge cases without being prompted is one of the clearest signals of LLD interview readiness.
Extensibility is the final dimension interviewers evaluate. A well-designed parking lot system should be extensible without modification: adding a new vehicle type (e.g., Electric Vehicle with a charging spot) should require adding a new class that extends Vehicle and a new spot type, not modifying existing classes. This is the Open/Closed Principle from SOLID — your design should be open for extension but closed for modification. If an interviewer asks 'how would you add EV charging spots?', a strong candidate describes adding a ChargingSpot that extends ParkingSpot and an ElectricVehicle that extends Vehicle, with the existing ParkingFloor and ParkingLot classes unchanged.
Parking Lot Class Diagram and 5 Key Design Decisions
Core classes: ParkingLot → ParkingFloor → ParkingSpot ← Vehicle (Motorcycle / Car / Truck) + Ticket. Key decisions: (1) Vehicle hierarchy via inheritance — ParkingSpot holds a Vehicle reference, not a concrete type. (2) Spot types as an enum or subclass — CompactSpot, LargeSpot, HandicappedSpot. (3) Floor selection strategy as a separate Strategy class — allows swapping nearest-available vs. lowest-floor-first without touching ParkingLot. (4) Ticket as a value object — issued at entry, redeemed at exit, contains spot reference and entry timestamp. (5) FullLotException for graceful capacity handling — never return null silently when the lot is full.
The Elevator System Problem — State Machines, Scheduling, and Concurrency
The elevator system LLD problem is the second canonical OOP design interview question and the most technically demanding of the three. It combines state machine design, scheduling algorithm selection, and concurrency awareness — three topics that reveal whether a candidate thinks about systems dynamically, not just statically. The parking lot has a largely static class structure; the elevator system is alive with moving state.
Start with requirements clarification: How many elevators? How many floors? Are requests from inside the elevator (floor buttons) handled differently from requests from hallway panels? Is there a weight or capacity limit? Should the system minimize wait time, energy consumption, or latency? These questions determine your scheduling algorithm choice and your concurrency model.
The core classes for the elevator system are: ElevatorController (top-level coordinator, receives requests, dispatches to elevators), Elevator (state machine — knows its current floor, direction, and set of pending stops), ElevatorRequest (value object — contains requested floor and direction for hallway requests, or destination floor for internal requests), Button (abstraction for both hallway panels and internal car panels), and Display (shows current floor and direction). The ElevatorController holds a scheduler component that implements the dispatch logic.
State transitions for a single Elevator are: IDLE (no pending requests) → MOVING_UP (has stops above current floor) → MOVING_DOWN (has stops below current floor) → STOPPED_DOOR_OPEN (at a requested floor, servicing passengers) → MOVING_UP or MOVING_DOWN or IDLE. Representing these transitions explicitly — rather than through ad-hoc boolean flags — is the OOP signal interviewers look for. Candidates who model elevator state as a collection of boolean fields (isMoving, isGoingUp, isDoorOpen) have not internalized state machine design.
The LOOK algorithm is the industry-standard scheduling algorithm for elevator systems and the one most interviewers expect candidates to know. LOOK works as follows: the elevator moves in one direction, servicing all stops in that direction before reversing. If there are no more stops in the current direction, the elevator reverses only if there are stops in the other direction — otherwise it becomes IDLE. LOOK is superior to the naive FCFS (first-come-first-served) approach because it dramatically reduces travel distance and wait time without the complexity of shortest-seek-time-first scheduling.
Concurrency is the third dimension of the elevator problem. In a real system, multiple hallway buttons are pressed simultaneously, elevator sensors report floor arrivals asynchronously, and the dispatcher must coordinate multiple elevators without race conditions. In an LLD interview, you are not expected to write thread-safe Java — you are expected to acknowledge the concurrency concerns: the request queue should be thread-safe, the Elevator state should be updated atomically, and the dispatcher should not assign the same request to two elevators. Naming these concerns and proposing solutions (synchronized queues, optimistic locking, or an event-driven dispatch model) demonstrates the senior engineering instinct that LLD interviewers are evaluating.
TinyURL / URL Shortener (LLD Version) — Class Design, Encoding, and Expiry
The URL shortener LLD problem is frequently confused with the URL shortener system design problem. The system design version asks about distributed storage, cache layers, read-heavy traffic, and global availability. The LLD version asks about class structure: what classes model the URL mapping, how base62 encoding is implemented, how collisions are handled, and how TTL expiry is enforced. A candidate who pivots to discussing horizontal sharding in an LLD interview has misread the question.
The core classes for the URL shortener LLD are: UrlShortener (entry point — exposes shorten(longUrl) and resolve(shortCode) methods), UrlMapping (value object — stores the short code, long URL, creation timestamp, and optional expiry time), UrlRepository (storage abstraction — in-memory HashMap for LLD purposes, swappable for a real persistence layer), Base62Encoder (utility class — implements the character set and encoding/decoding logic), and ExpiryManager (handles TTL enforcement — either eagerly at resolve time or via a background cleanup mechanism).
Base62 encoding is the standard approach for generating short codes. The base62 character set is [a-z, A-Z, 0-9] — 62 characters. A 6-character base62 code produces 62^6 = approximately 56 billion unique codes, sufficient for any practical URL shortener. The encoding algorithm takes an integer ID (assigned sequentially or randomly), converts it to base62 by repeatedly taking modulo 62 and looking up the character, and pads to the desired length. The decoding algorithm reverses this: convert each character to its index, multiply by 62^position, and sum.
Collision handling in the URL shortener LLD is a nuance that separates strong candidates. With sequential IDs, there are no collisions by definition — each URL gets a unique incrementing ID. But sequential IDs expose the total URL count (a privacy leak) and are predictable. Random ID generation avoids these issues but introduces collision probability. The canonical LLD solution is: generate a random code, check for existence in the repository, and retry with a new random code on collision. With a 56-billion code space and a few million URLs, the collision probability per generation is negligible, making this retry approach correct and practical.
TTL expiry for the URL shortener requires a design decision: lazy expiry or eager expiry. Lazy expiry checks the expiry timestamp at resolve time and returns a 404 if the URL has expired — simple to implement, but expired URLs accumulate in storage. Eager expiry uses a background cleanup thread or scheduled job to delete expired URLs — more complex, but keeps storage clean. In an LLD interview, describe both approaches, state the trade-off, and choose lazy expiry as the simpler implementation for the given context. Interviewers reward candidates who make explicit, reasoned trade-off decisions over candidates who implement the most complex approach without justification.
The 5-Step OOP Design Framework — Works on Every LLD Problem
Every LLD interview, regardless of the specific system, can be approached with the same 5-step framework. Candidates who have internalized this framework never stare blankly at a new prompt — they immediately know where to start and what order to work through. The framework is: (1) Clarify requirements, (2) Identify entities, (3) Define relationships, (4) Add behaviors, (5) Handle edge cases. These steps are sequential and deliberate — skipping or reordering them is the root cause of most LLD interview failures.
Step 1 — Clarify requirements: Before touching a class diagram or writing code, ask the four canonical clarification questions: What are the core use cases? (What must the system do?) What are the constraints? (Single instance or multi-tenant? In-memory or persistent?) What are the non-functional requirements? (Concurrency? Performance?) What is out of scope? (No authentication, no payment processing.) Spending 3–5 minutes on requirements signals professional engineering discipline and prevents you from building the wrong thing.
Step 2 — Identify entities: List every real-world noun in the problem domain that has independent state and behavior. For the parking lot: ParkingLot, ParkingFloor, ParkingSpot, Vehicle, Ticket. For the elevator: Elevator, ElevatorRequest, Button, Display. For the URL shortener: UrlMapping, UrlRepository, Base62Encoder. Entities become your classes. Not every noun is a class — 'fee' might be an attribute of Ticket rather than its own class. Use judgment to distinguish entities from value attributes.
Step 3 — Define relationships: For each pair of entities, determine the relationship type: composition (strong ownership — ParkingLot owns ParkingFloors), aggregation (loose association — Elevator knows about Requests but doesn't own them), inheritance (is-a — Car is-a Vehicle), or interface implementation (can-do — Schedulable for objects that can be dispatched). Draw or state these relationships explicitly. Interviewers evaluate whether you can describe why a relationship is composition versus aggregation — the answer reveals whether you understand object lifetime and ownership.
Step 4 — Add behaviors: For each class, define the methods that implement the system's use cases. ParkingLot.parkVehicle(vehicle) → Ticket. ParkingFloor.findAvailableSpot(vehicleType) → Optional<ParkingSpot>. ParkingSpot.occupy(vehicle) → void. Elevator.addStop(floor) → void. UrlShortener.shorten(longUrl) → String. Behaviors should be defined at the interface level first — what does the method take and return — before discussing implementation. This keeps the design conversation at the right level of abstraction.
Step 5 — Handle edge cases: After defining the happy path, systematically walk through failure scenarios. What if a resource is unavailable? (Parking lot full, elevator overweight, short code collision.) What if input is invalid? (Null vehicle, negative floor, empty URL.) What if the system is used concurrently? (Two threads booking the same parking spot, two elevator requests dispatched simultaneously.) Addressing edge cases without prompting is the single strongest signal of LLD interview readiness. Most candidates stop at Step 4; strong candidates always reach Step 5.
- 1Step 1: Clarify requirements — 4 questions: use cases, constraints, non-functional requirements, out of scope
- 2Step 2: Identify entities — list real-world nouns with independent state and behavior; these become your classes
- 3Step 3: Define relationships — composition, aggregation, inheritance, or interface; state the reason for each choice
- 4Step 4: Add behaviors — define method signatures (inputs and outputs) at the interface level before implementation
- 5Step 5: Handle edge cases — unavailability, invalid input, concurrent access; addressing these unprompted is the top signal
Common OOP Design Mistakes to Avoid
Over-engineering: adding abstract factory patterns, visitor patterns, or three-level inheritance hierarchies for a system with two concrete types. Skipping requirements: jumping to class design before establishing what the system must do — this produces a technically correct design for the wrong problem. Missing edge cases: stopping at the happy path and never addressing full capacity, invalid input, or concurrent access. God classes: dumping all behavior into a single ParkingLot class instead of distributing responsibilities across ParkingFloor, ParkingSpot, and Ticket. Anemic models: classes with only getters and setters and no real behavior — this is a data structure, not an object-oriented design.
Conclusion: LLD Is Learnable — the 5-Step Framework Works on Every Problem
Low-level design interviews reward structured thinking more than raw coding ability. A candidate who applies the 5-step OOP design framework — clarify requirements, identify entities, define relationships, add behaviors, handle edge cases — to any LLD prompt will produce a coherent, extensible design even on a problem they have never seen before. This is the central insight of LLD preparation: the framework is the skill, not the memorization of specific class diagrams.
The three canonical problems in this guide — the parking lot, the elevator system, and the URL shortener — are worth studying in depth not because they will be asked verbatim, but because each one exercises a different muscle. The parking lot exercises composition and inheritance. The elevator exercises state machines and scheduling algorithms. The URL shortener exercises encoding, collision handling, and trade-off reasoning. Together they cover the full vocabulary of OOP design that LLD interviewers draw on.
The parking lot LLD problem is the most common entry point to LLD interview preparation, and for good reason: it is maximally illustrative. If you can describe the parking lot's five-class structure, explain why ParkingFloor is a composition rather than an aggregation of ParkingSpots, articulate how a new vehicle type would be added without modifying existing classes, and address the edge cases of a full lot and a lost ticket, you have demonstrated the OOP modeling fluency that LLD interviewers are looking for.
The most important preparation shift for candidates coming from pure LeetCode prep is recognizing that LLD interviews are not about algorithmic correctness — they are about design vocabulary and reasoning. You will not be asked to implement a binary search or a shortest path algorithm. You will be asked to make and defend design decisions: why composition over inheritance here, why a Strategy pattern for the floor selection algorithm, why lazy expiry over eager expiry for the URL shortener. Preparing answers to the 'why' questions is as important as knowing the class structures themselves.
YeetCode reinforces the design vocabulary that LLD interviews require through pattern-based problem sets organized by OOP principle. The curated tracks for object-oriented design cover composition, inheritance, interface design, and behavioral patterns — the same concepts that appear in parking lot, elevator, and URL shortener interview questions. Use the spaced repetition system to internalize design patterns across multiple problem types, not just the three canonical problems in this guide.