system-design

Design a Parking Lot (Low-Level / OOP Design)

Difficulty: Tier 1 (LLD) Asked at: Tkxel, Systems Ltd, Arbisoft, Amazon, most product companies Time budget: 45 min

A change of pace: this is a low-level design (LLD) question — no servers, no scale, no databases. It’s about classes: their responsibilities, relationships, and whether your design extends cleanly when the interviewer adds “now support electric vehicles” at minute 35. The parking lot is the canonical LLD problem; master its shape and elevator, vending machine, and the rest pattern-match.

Prerequisites: Low-Level Design Round, Layered & Hexagonal


1. Requirements (clarify first — same as any round)

Functional:

Constraints to clarify: payment methods? multiple entry/exit gates? handicapped/EV/reserved spots? pricing model (hourly/flat)? Scope it, then design.


2. Identify the entities (the nouns → classes)

ParkingLot, ParkingFloor, ParkingSpot, Vehicle, Ticket, Payment, EntryGate, ExitGate, ParkingRate

🚨 Nouns in the problem become classes — a reliable heuristic for finding entities.


3. Relationships

ParkingLot        has-many  ParkingFloor
ParkingFloor      has-many  ParkingSpot
Vehicle           is-a      Motorcycle | Car | Truck        (inheritance)
ParkingSpot       is-a      SmallSpot | MediumSpot | LargeSpot
Ticket            associates a Vehicle with a ParkingSpot + entry time
Payment           settles   a Ticket
ParkingRate (Strategy)      computes fee from duration/type

4. Class design

enum VehicleType { MOTORCYCLE, CAR, TRUCK }
enum SpotType    { SMALL, MEDIUM, LARGE }

abstract class Vehicle {
    String licensePlate
    VehicleType type
}
class Car extends Vehicle { type = CAR }
class Motorcycle extends Vehicle { type = MOTORCYCLE }
class Truck extends Vehicle { type = TRUCK }

abstract class ParkingSpot {
    String id
    SpotType type
    boolean isAvailable
    Vehicle currentVehicle
    abstract boolean canFit(Vehicle v)   // size compatibility
    void assign(Vehicle v)
    void remove()
}
class SmallSpot  extends ParkingSpot { canFit → v.type == MOTORCYCLE }
class MediumSpot extends ParkingSpot { canFit → v.type in (MOTORCYCLE, CAR) }
class LargeSpot  extends ParkingSpot { canFit → any type }

class Ticket {
    String id
    Vehicle vehicle
    ParkingSpot spot
    DateTime entryTime, exitTime
}

interface PricingStrategy { double calculate(Ticket t) }   // Strategy pattern
class HourlyPricing implements PricingStrategy { ... }
class FlatPricing   implements PricingStrategy { ... }

class ParkingFloor {
    List<ParkingSpot> spots
    ParkingSpot findAvailableSpot(Vehicle v)   // first spot where canFit && isAvailable
}

class ParkingLot {                              // orchestrator (often a Singleton)
    List<ParkingFloor> floors
    PricingStrategy pricing
    Ticket parkVehicle(Vehicle v)               // find spot across floors, assign, issue ticket
    Payment unpark(Ticket t)                    // free spot, compute fee, take payment
}

5. Where the design patterns fit

🚨 Apply patterns because they fit, not to show off.

Pattern Where Why
Strategy PricingStrategy Swap pricing (hourly/flat/type-based) without touching ParkingLot → open/closed
Factory Creating Vehicle/ParkingSpot by type Centralize construction
Singleton ParkingLot instance One lot object (use judiciously)
Observer Notify a display board / waiting driver when a spot frees Decouple notification

6. The extension test (what’s actually graded)

🚨 The interviewer will add a requirement at minute 35. Your design passes if it absorbs it by adding code, not editing existing code (open/closed).

If adding EV spots forces you to edit a giant if (type == ...) switch, your design failed the test — that’s why polymorphism over conditionals matters here.


7. Concurrency (name it, briefly)

Two cars racing for the last spot: assign() must be atomic (lock the spot or use an atomic compare-and-set on isAvailable) so only one succeeds. Multiple gates → the ParkingLot must be thread-safe. Mentioning this shows real-world awareness even in an LLD round.


8. Trade-offs & design-quality summary

Choice Good design Weak design
Vehicle/spot types Polymorphism (subclasses / canFit) if/switch on an enum everywhere
Pricing Strategy interface (swappable) Hardcoded in unpark()
Extensibility Add EV spot = new class Add EV spot = edit existing classes
Spot capabilities Composition where combinatorial Deep inheritance explosion
Encapsulation Spot manages its own state External code flips flags directly

9. Follow-up questions

Why prefer canFit() polymorphism over a switch on vehicle type? Because a switch statement on type, repeated wherever size compatibility is checked, violates the open/closed principle: adding a new vehicle or spot type means hunting down and editing every switch, which is error-prone (miss one and you have a bug) and risks breaking working code. Making compatibility a polymorphic method — each spot subclass implements `canFit(vehicle)` — means the decision lives in one place per type and adding a new type is adding a new subclass, touching nothing that already works. This is the core LLD signal: the design extends by addition, not modification.
Should ParkingLot really be a Singleton? It's defensible if there's genuinely one lot the whole program shares, and it's a common textbook answer. But be cautious: Singletons are effectively global state, which complicates testing (you can't easily substitute a fresh lot) and concurrency. A cleaner alternative is to create one instance and inject it where needed (dependency injection), getting "one shared lot" without the global-access downsides. Mentioning this nuance — that Singleton is convenient but has testability costs — is a stronger answer than reflexively reaching for it.
How would you support multiple entry and exit gates? Model `EntryGate` and `ExitGate` as classes that call `ParkingLot.parkVehicle()` / `unpark()`. Multiple gates operate concurrently on the shared lot, so spot assignment must be thread-safe — `assign()` uses a lock or atomic compare-and-set on the spot's availability so two gates can't hand the same spot to two vehicles. The gates themselves are thin; the lot is the coordination point.
How do you find the nearest available spot, not just any? Enrich `findAvailableSpot` with a selection policy — e.g. keep per-floor, per-size available-spot indexes (a priority queue or sorted structure keyed by distance from the entrance) so you can pop the nearest compatible free spot in O(log n) instead of scanning. Making spot-selection a strategy lets you swap "nearest," "cheapest," or "any" policies without changing the rest of the design — again, extension by addition.

10. What junior / mid / senior answers look like


Further reading