LLD for Parking Lot System: Designing a Smart Garage
The Mall Garage Problem
Entry gate: You press a button β get a ticket β gate opens.
π― System Scenario & Requirements
System Goal
Entry gate: You press a button β get a ticket β gate opens.
Actors & Components
- Application or API tier
- Core service under design
- Operator or SRE observer
Functional Requirements
- Entry gate: You press a button β get a ticket β gate opens.
- Parking: Find a spot (the system might display "Floor 2, Spot 14").
- Exit: Scan your ticket β system calculates fee based on time parked β gate opens.
Constraints & Assumptions
β‘ Execution Stages & Decision Trace
Stage 1 β Parking Lot Design Basics
Before writing any code, it helps to map out the real-world entities the system must represent.
Spot Types β Not every bay fits every vehicle.
Deep Dive
Scenario context
You pull up to a mall garage. Three things happen:
- Entry gate: You press a button β get a ticket β gate opens.
- Parking: Find a spot (the system might display "Floor 2, Spot 14").
- Exit: Scan your ticket β system calculates fee based on time parked β gate opens.
The software behind this manages hundreds of concurrent vehicles across multiple floors, spot types, and pricing tiers.
This walkthrough focuses on The Mall Garage Problem.
Before making a design choice, clarify:
- Entry gate: You press a button β get a ticket β gate opens.
- Parking: Find a spot (the system might display "Floor 2, Spot 14").
- Exit: Scan your ticket β system calculates fee based on time parked β gate opens.
Concepts in play: tree-data-structures, low-level-design-systems, vector-databases-rag, design-patterns-gof
Stage 1 β Parking Lot Design Basics
- Before writing any code, it helps to map out the real-world entities the system must represent.
- Spot Types β Not every bay fits every vehicle.
- A typical garage divides its spaces into three tiers: Vehicle Types β Each vehicle carries a licensePlate and a VehicleType enum ( BIKE , CAR , TRUCK ).
- The type determines which spot tier it can occupy β a TRUCK cannot squeeze into a CompactSpot .
Observable outcome: The type determines which spot tier it can occupy β a TRUCK cannot squeeze into a CompactSpot .
Stage 2 β The Domain Model
- Hierarchy: - ParkingSpot β subclasses: BikeSpot , CompactSpot , LargeSpot - Vehicle β subclasses: Bike , Car , Truck - ParkingLot β Singleton (one physical lot, one controller)
classDiagram
class ParkingLot {
-static ParkingLot instance
+List~ParkingFloor~ floors
-PricingStrategy pricingStrategy
+Ticket park(Vehicle v)
+double exit(Ticket t)
}
class ParkingFloor {
+int floorNumber
-Map~VehicleType,PriorityQueue~ heaps
+ParkingSpot findNearest(VehicleType t)
+void releaseSpot(ParkingSpot s)
}
class ParkingSpot {
<>
+int id
+boolean isFree
+VehicleType type
+void assign(Vehicle v)
+void free()
}
class BikeSpot {
+VehicleType type = BIKE
}
class CompactSpot {
+VehicleType type = CAR
}
class LargeSpot {
+VehicleType type = TRUCK
}
class Vehicle {
<>
+String licensePlate
+VehicleType type
}
class Bike
class Car
class Truck
class Ticket {
+Vehicle vehicle
+ParkingSpot spot
+Instant entryTime
}
class PricingStrategy {
<>
+calculateFee(VehicleType type, long minutes) double
}
class HourlyPricing {
+calculateFee(VehicleType type, long minutes) double
}
ParkingLot "1" *-- "many" ParkingFloor : owns
ParkingFloor "1" *-- "many" ParkingSpot : contains
BikeSpot --|> ParkingSpot : extends
CompactSpot --|> ParkingSpot : extends
LargeSpot --|> ParkingSpot : extends
Bike --|> Vehicle : extends
Car --|> Vehicle : extends
Truck --|> Vehicle : extends
Ticket --> Vehicle : records
Ticket --> ParkingSpot : records
ParkingLot o-- PricingStrategy : uses
HourlyPricing ..|> PricingStrategy : implements
Observable outcome: Hierarchy: - ParkingSpot β subclasses: BikeSpot , CompactSpot , LargeSpot - Vehicle β subclasses: Bike , Car , Truck - ParkingLot β Singleton (one physical lot, one controller)
Stage 3 β OOP Pillars in the Parking Lot Domain
- The four OOP pillars are not abstract ideals here β each one maps to a concrete class or interface in this design.
- Encapsulation β ParkingFloor hides its Min-Heap ParkingFloor owns a Map<VehicleType, PriorityQueue<ParkingSpot internally.
- No external class ever touches that map directly.
- The public surface is exactly two methods: findNearest(VehicleType) (wraps poll() ) and releaseSpot(ParkingSpot) (wraps offer() ).
stateDiagram-v2
[*] --> AVAILABLE : spot added to Min-Heap
AVAILABLE --> OCCUPIED : findNearest() assign(vehicle)
OCCUPIED --> AVAILABLE : releaseSpot() re-insert into heap
AVAILABLE --> [*] : floor decommissioned
Observable outcome: The public surface is exactly two methods: findNearest(VehicleType) (wraps poll() ) and releaseSpot(ParkingSpot) (wraps offer() ).
Stage 4 β Finding the Nearest Spot: Min-Heap
- A naive O(N) linear scan through all spots is too slow for a large garage.
- Optimized: Use a Min-Heap (priority queue ordered by spot ID, lowest = nearest entrance) per vehicle type per floor.
- Operations: - findNearest(CAR) β heaps.get(CAR).poll() β O(log N) - releaseSpot(spot) β heaps.get(spot.type).offer(spot) β O(log N) For N = 1000 spots per floor: logβ(1000) β 10 operations vs.
Floor 1 Compact Heap: [101, 103, 107, ...] β pop 101 in O(log N)
Floor 1 Large Heap: [150, 155, ...]
Observable outcome: Operations: - findNearest(CAR) β heaps.get(CAR).poll() β O(log N) - releaseSpot(spot) β heaps.get(spot.type).offer(spot) β O(log N) For N = 1000 spots per floor: logβ(1000) β 10 operations vs.
Stage 5 β Deep Dive: Factory and Strategy Patterns
- Callers use VehicleFactory.create(VehicleType.CAR, "ABC-123") β never new Car(...) directly.
- This decouples object creation from business logic and makes it easy to add ElectricVehicle later.
public class VehicleFactory {
public static Vehicle create(VehicleType type, String plate) {
return switch (type) {
case BIKE -> new Bike(plate);
case CAR -> new Car(plate);
case TRUCK -> new Truck(plate);
};
}
}
Observable outcome: This decouples object creation from business logic and makes it easy to add ElectricVehicle later.
Stage 6 β Strategy Pattern for Pricing
- Pricing rules vary (hourly, daily, weekend, EV charging surcharge): The ParkingLot holds a reference to any PricingStrategy .
- Swapping from HourlyPricing to DynamicPricing requires zero changes to the rest of the code.
- π Factory and Strategy: Class Diagram This class diagram extends the core domain model to show the full Factory and Strategy pattern integration, including the VehicleFactory creation point, the pluggable PricingStrateβ¦
- The key relationship is that ParkingLot depends on interfaces ( PricingStrategy , SpotAllocator ) rather than concrete classes, which is what allows EVSpot and ChargingPricing to be added without modifying any existingβ¦
public interface PricingStrategy {
double calculateFee(VehicleType type, long parkingMinutes);
}
public class HourlyPricing implements PricingStrategy {
public double calculateFee(VehicleType type, long minutes) {
double ratePerHour = type == VehicleType.TRUCK ? 3.50 :
type == VehicleType.CAR ? 2.00 : 1.00;
return ratePerHour * Math.ceil(minutes / 60.0);
}
}
classDiagram
class VehicleFactory {
+create(VehicleType, String) Vehicle
}
class PricingStrategy {
<>
+calculateFee(VehicleType, long) double
}
class HourlyPricing {
+calculateFee(VehicleType, long) double
}
class ChargingPricing {
+calculateFee(VehicleType, long) double
}
class SpotAllocator {
<>
+findNearest(VehicleType, List~ParkingFloor~) Optional~ParkingSpot~
+release(ParkingSpot, List~ParkingFloor~) void
}
class NearestFirstAllocator {
+findNearest(VehicleType, List~ParkingFloor~) Optional~ParkingSpot~
+release(ParkingSpot, List~ParkingFloor~) void
}
class EVSpot {
+chargerType String
+canFit(VehicleType) boolean
}
class ParkingLot {
-PricingStrategy pricingStrategy
-SpotAllocator allocator
+park(Vehicle) Ticket
+exit(Ticket) double
}
HourlyPricing ..|> PricingStrategy : implements
ChargingPricing ..|> PricingStrategy : implements
NearestFirstAllocator ..|> SpotAllocator : implements
ParkingLot o-- PricingStrategy : uses (injected)
ParkingLot o-- SpotAllocator : uses (injected)
VehicleFactory --> Vehicle : creates
EVSpot --|> ParkingSpot : extends
Observable outcome: The key relationship is that ParkingLot depends on interfaces ( PricingStrategy , SpotAllocator ) rather than concrete classes, which is what allows EVSpot and ChargingPricing to be added without modifying any existingβ¦
End-to-end outcome
Entry gate: You press a button β get a ticket β gate opens.
Alternatives considered
Decision Guide: When to Apply These Parking Lot Patterns
Start simple: a flat HashMap<SpotId, Spot works for a garage under 50 spots.
Failure and degraded modes
Trade-offs & Failure Modes in Parking Lot Design
- Detection: Trade-offs & Failure Modes in Parking Lot Design
- User or operator impact: Trade-offs & Failure Modes in Parking Lot Design
