Designing a parking lot
In this series (15 parts)
- Introduction to low level design
- SOLID principles
- Design patterns: Creational
- Design patterns: Structural
- Design patterns: Behavioral
- Designing a parking lot
- Designing a library management system
- Designing an elevator system
- Designing a hotel booking system
- Designing a ride-sharing model
- Designing a rate limiter
- Designing a logging framework
- Designing a notification system
- API design and contract-first development
- Data modeling for system design
A parking lot seems trivial until you sit down and model it. Multiple vehicle types, multiple floors, concurrent entry and exit, payment calculation, real-time spot tracking. The domain is small enough to fit in your head yet rich enough to exercise every core OOP skill.
Requirements gathering
Before writing a single class, pin down what the system must do. This is the step most candidates rush through in an interview, and it costs them.
Functional requirements:
- The parking lot has multiple floors. Each floor has multiple spots.
- Spots come in three sizes: compact, regular, and large.
- Vehicles come in three types: motorcycle, car, and truck. A vehicle can only park in a spot that fits its type or larger.
- On entry, the system assigns the nearest available spot and issues a ticket.
- On exit, the system calculates the fee based on duration and collects payment.
- The system tracks real-time availability per floor and per spot type.
Non-functional requirements:
- Thread-safe spot allocation (two cars arriving simultaneously must not get the same spot).
- The design should be extensible: adding an EV charging spot type should not require rewriting existing classes.
Identifying entities
Read the requirements again and underline the nouns. You get: parking lot, floor, spot, vehicle, ticket, payment. Each noun is a candidate class. Some will become enums (vehicle type, spot type, payment status). Here is the full list:
| Entity | Role |
|---|---|
| ParkingLot | Top-level aggregate, holds floors |
| Floor | Holds a collection of spots |
| ParkingSpot | Individual spot with type and occupancy state |
| Vehicle | Represents a parked vehicle |
| Ticket | Issued on entry, closed on exit |
| Payment | Calculates and records fees |
Class diagram
classDiagram
class ParkingLot {
-String id
-String name
-List~Floor~ floors
+addFloor(Floor)
+getAvailableSpots(VehicleType) int
+assignSpot(Vehicle) Ticket
+processExit(Ticket) Payment
}
class Floor {
-int floorNumber
-List~ParkingSpot~ spots
+getAvailableSpots(SpotType) List~ParkingSpot~
+findFirstAvailable(SpotType) ParkingSpot
}
class ParkingSpot {
-String spotId
-int spotNumber
-SpotType type
-boolean occupied
-Vehicle currentVehicle
+isAvailable() boolean
+assignVehicle(Vehicle) void
+removeVehicle() Vehicle
}
class Vehicle {
-String licensePlate
-VehicleType type
}
class Ticket {
-String ticketId
-Vehicle vehicle
-ParkingSpot spot
-DateTime entryTime
-DateTime exitTime
-TicketStatus status
+closeTicket(DateTime) void
}
class Payment {
-String paymentId
-Ticket ticket
-double amount
-PaymentMethod method
-PaymentStatus status
+calculate(HourlyRate) double
+processPayment() boolean
}
class SpotType {
<<enumeration>>
COMPACT
REGULAR
LARGE
}
class VehicleType {
<<enumeration>>
MOTORCYCLE
CAR
TRUCK
}
class TicketStatus {
<<enumeration>>
ACTIVE
PAID
LOST
}
class PaymentStatus {
<<enumeration>>
PENDING
COMPLETED
FAILED
}
ParkingLot "1" *-- "many" Floor
Floor "1" *-- "many" ParkingSpot
ParkingSpot "1" o-- "0..1" Vehicle
Ticket "1" --> "1" Vehicle
Ticket "1" --> "1" ParkingSpot
Payment "1" --> "1" Ticket
ParkingSpot --> SpotType
Vehicle --> VehicleType
Full class diagram for the parking lot system.
Mapping vehicle types to spot types
A motorcycle fits in any spot. A car fits in regular or large. A truck needs large. Encode this as a simple lookup rather than a chain of if-else blocks.
public class VehicleSpotMapper {
private static final Map<VehicleType, List<SpotType>> ALLOWED = Map.of(
VehicleType.MOTORCYCLE, List.of(SpotType.COMPACT, SpotType.REGULAR, SpotType.LARGE),
VehicleType.CAR, List.of(SpotType.REGULAR, SpotType.LARGE),
VehicleType.TRUCK, List.of(SpotType.LARGE)
);
public static List<SpotType> getAllowedSpots(VehicleType v) {
return ALLOWED.get(v);
}
}
This is the Strategy pattern in disguise. If pricing varies by spot type, the same table-driven approach works.
Spot allocation algorithm
Walk each floor from the bottom up. On each floor, iterate through spots of compatible types in order. Return the first available one. This gives “nearest to entrance” behavior when the entrance is on the ground floor.
public Ticket assignSpot(Vehicle vehicle) {
List<SpotType> allowed = VehicleSpotMapper.getAllowedSpots(vehicle.getType());
for (Floor floor : floors) {
for (SpotType type : allowed) {
ParkingSpot spot = floor.findFirstAvailable(type);
if (spot != null) {
spot.assignVehicle(vehicle);
return new Ticket(vehicle, spot, LocalDateTime.now());
}
}
}
throw new ParkingFullException("No available spot for " + vehicle.getType());
}
Concurrency for spot allocation
Two threads calling assignSpot at the same time can both see the same spot as available, then both assign to it. Classic race condition.
Option 1: Synchronized block. Wrap the allocation loop in a synchronized block on the floor object. Simple, but serializes all allocations on a given floor.
Option 2: Atomic compare-and-set. Give each ParkingSpot an AtomicBoolean for occupancy. The assignVehicle method uses compareAndSet(false, true). If it fails, the caller moves to the next spot.
public boolean assignVehicle(Vehicle vehicle) {
if (occupied.compareAndSet(false, true)) {
this.currentVehicle = vehicle;
return true;
}
return false;
}
Option 2 is lock-free and allows genuine parallelism across spots. It is the preferred approach for high-throughput lots.
Data model (ER diagram)
When you persist this system to a relational database, the class hierarchy flattens into tables.
erDiagram
PARKING_LOT ||--|{ FLOOR : contains
FLOOR ||--|{ PARKING_SPOT : contains
PARKING_SPOT ||--o| VEHICLE : holds
TICKET ||--|| PARKING_SPOT : references
TICKET ||--|| VEHICLE : issuedFor
PAYMENT ||--|| TICKET : settles
PARKING_LOT {
string id PK
string name
string address
}
FLOOR {
int floor_number PK
string lot_id FK
int total_spots
}
PARKING_SPOT {
string spot_id PK
int floor_number FK
string spot_type
boolean is_occupied
}
VEHICLE {
string license_plate PK
string vehicle_type
}
TICKET {
string ticket_id PK
string license_plate FK
string spot_id FK
datetime entry_time
datetime exit_time
string status
}
PAYMENT {
string payment_id PK
string ticket_id FK
decimal amount
string method
string status
}
Entity-relationship diagram for the parking lot database schema.
Fee calculation
Keep the pricing strategy separate from the Ticket class. A FeeCalculator interface allows flat-rate, hourly, and tiered models to coexist.
public interface FeeCalculator {
double calculate(Ticket ticket);
}
public class HourlyFeeCalculator implements FeeCalculator {
private final Map<SpotType, Double> rates;
public HourlyFeeCalculator(Map<SpotType, Double> rates) {
this.rates = rates;
}
public double calculate(Ticket ticket) {
long hours = ChronoUnit.HOURS.between(ticket.getEntryTime(), ticket.getExitTime());
if (hours == 0) hours = 1; // minimum one hour
double rate = rates.get(ticket.getSpot().getType());
return hours * rate;
}
}
Exit flow
- The driver presents the ticket (scans a barcode or enters the ticket ID).
- The system looks up the ticket, records the exit time, and calculates the fee.
- The driver pays (cash, card, or UPI).
- On successful payment, the spot is freed and the ticket is marked
PAID. - The barrier opens.
If the ticket is lost, charge the maximum daily rate and mark the ticket LOST.
Extensibility checklist
| Change | Impact |
|---|---|
| Add EV charging spots | New SpotType enum value, new entry in mapper |
| Add valet parking | New ValetService class, Ticket gains a flag |
| Multi-entry/multi-exit gates | Entry/exit gate classes, each calls assignSpot |
| Dynamic pricing | New FeeCalculator implementation |
None of these require modifying existing classes. That is the Open/Closed Principle at work.
What comes next
The library management system introduces state machines for tracking object lifecycles, something the parking lot’s simple occupied/free toggle does not need. It also brings search functionality and fine calculation into the mix.