LLD · Classic
Parking Lot System
Design classes for a multi-floor parking lot: park a vehicle, find a spot, calculate fee, and free a spot on exit.
Simple summary: On entry, find a free spot that fits the
vehicle and issue a ticket. On exit, use that ticket to calculate the fee,
close the ticket, and free the spot.
1. Clarify requirements
- Scale: 1 building, 3 floors, 100 spots total.
- Vehicle types: Motorcycle, car, and truck.
- Spot types: Compact, regular, and large.
- Entry: Issue a ticket with the vehicle, spot, and entry time.
- Exit: Calculate the fee, close the ticket, and free the spot.
- Out of scope: Reservations, valet service, and payment gateway details.
2. Core entities
- ParkingLot — owns floors; entry/exit APIs.
- ParkingFloor — collection of spots on one level.
- ParkingSpot — spot ID, type, and current vehicle.
- Vehicle — plate number and type.
- Ticket — ticket ID, spot, vehicle, entry time, and status.
3. Class design
enum VehicleType { MOTORCYCLE, CAR, TRUCK }
enum SpotType { COMPACT, REGULAR, LARGE }
enum TicketStatus { ACTIVE, CLOSED }
class ParkingSpot {
id
type
vehicle = null
isFree()
canFit(vehicle)
park(vehicle)
free()
}
class ParkingFloor {
id
spots
}
class ParkingLot {
floors
activeTickets
issueTicket(vehicle)
exit(ticketId, exitTime)
}
4. Vehicle and spot compatibility
| Vehicle | Allowed spots |
|---|---|
| Motorcycle | Compact, regular, or large |
| Car | Regular or large |
| Truck | Large only |
Choose the smallest compatible spot first so larger spots remain available for larger vehicles.
5. Spot assignment
Ticket issueTicket(Vehicle vehicle):
for each floor in order:
for each compatible spot type, smallest first:
spot = floor.findFreeSpot(spotType)
if spot != null:
spot.park(vehicle)
ticket = new Ticket(vehicle, spot, now())
activeTickets[ticket.id] = ticket
return ticket
return null // lot full
A production version could inject a spot-assignment strategy, but an ordered scan is enough for this scope.
6. Exit and fee
Receipt exit(ticketId, exitTime):
ticket = activeTickets[ticketId]
if ticket == null or ticket.status == CLOSED:
return error("Invalid ticket")
hours = ceil((exitTime - ticket.entryTime) / 1 hour)
fee = max(1, hours) * hourlyRate(ticket.vehicle.type)
ticket.spot.free()
ticket.status = CLOSED
remove activeTickets[ticketId]
return new Receipt(ticket.id, exitTime, fee)
Example: Car enters at 10:00, exits at 12:30 → 3 billable hours × $5 = $15.
7. End-to-end walkthrough
1. Car (plate ABC, type CAR) arrives at entry gate
2. issueTicket(car) scans floors → finds free REGULAR spot B2-14
3. The spot stores the car; the lot issues Ticket(T-101, B2-14, 10:00)
4. At 12:30, driver presents Ticket T-101 at exit
5. exit(T-101, 12:30) → free B2-14 → 3 hours × $5 = $15 receipt
6. Spot B2-14 is free again for the next car
8. Edge cases and concurrency
- Return a clear “lot full” result when no compatible spot exists.
- Reject an unknown, already closed, or mismatched ticket.
- Use the server clock, not a client-provided entry time.
- Reserve a spot atomically so two entry gates cannot assign the same spot.
Quick revision
- Entities: ParkingLot, ParkingFloor, ParkingSpot, Vehicle, Ticket, and Receipt.
- Entry: Find the smallest compatible free spot and issue an active ticket.
- Exit: Validate the ticket, calculate the fee, close it, and free the spot.
- Matching: Motorcycles fit any spot, cars fit regular or large, and trucks need large.
- Fee: Round the duration up to billable hours and apply the vehicle rate.
- Concurrency: Spot assignment must be atomic.