Parking Lot System - Low-Level Design
Problem Statement
Design a parking lot system that can accommodate different types of vehicles (motorcycle, car, truck) with different parking spot sizes (compact, regular, large).
Requirements
Functional Requirements
- Multiple floors with multiple parking spots
- Different spot sizes: Compact, Regular, Large
- Different vehicle types: Motorcycle, Car, Truck
- Entry/exit gates with ticket generation
- Parking fee calculation based on duration
- Display available spots at entrance
- Support for reserved/handicapped spots
Non-Functional Requirements
- Thread-safe for concurrent access
- Extensible for new vehicle types
- Follow SOLID principles
- Low latency for spot allocation
Class Diagram
Main Class
+ properties
+ attributes
+ methods()
+ operations()
Helper Class
+ fields
+ data
+ helpers()
+ utilities()
Interface
«interface»
+ abstract methods()
━━━▶
Dependency
◆━━━
Composition
△━━━
Inheritance
Design Patterns Used
- Singleton Pattern: ParkingLot instance
- Factory Pattern: Vehicle and ParkingSpot creation
- Strategy Pattern: Parking fee calculation
- Observer Pattern: Update availability displays
Core Classes
java
1// Enums
2public enum VehicleType {
3 MOTORCYCLE, CAR, TRUCK
4}
5
6public enum SpotType {
7 COMPACT, REGULAR, LARGE
8}
9
10public enum SpotStatus {
11 AVAILABLE, OCCUPIED, RESERVED
12}
13
14// Vehicle Hierarchy
15public abstract class Vehicle {
16 protected String licensePlate;
17 protected VehicleType type;
18
19 public Vehicle(String licensePlate, VehicleType type) {
20 this.licensePlate = licensePlate;
21 this.type = type;
22 }
23
24 public abstract boolean canFitInSpot(ParkingSpot spot);
25
26 // Getters
27 public String getLicensePlate() { return licensePlate; }
28 public VehicleType getType() { return type; }
29}
30
31public class Motorcycle extends Vehicle {
32 public Motorcycle(String licensePlate) {
33 super(licensePlate, VehicleType.MOTORCYCLE);
34 }
35
36 @Override
37 public boolean canFitInSpot(ParkingSpot spot) {
38 // Motorcycle can fit in any spot
39 return true;
40 }
41}
42
43public class Car extends Vehicle {
44 public Car(String licensePlate) {
45 super(licensePlate, VehicleType.CAR);
46 }
47
48 @Override
49 public boolean canFitInSpot(ParkingSpot spot) {
50 return spot.getType() == SpotType.REGULAR ||
51 spot.getType() == SpotType.LARGE;
52 }
53}
54
55public class Truck extends Vehicle {
56 public Truck(String licensePlate) {
57 super(licensePlate, VehicleType.TRUCK);
58 }
59
60 @Override
61 public boolean canFitInSpot(ParkingSpot spot) {
62 return spot.getType() == SpotType.LARGE;
63 }
64}Parking Spot
java
1public class ParkingSpot {
2 private final String spotId;
3 private final SpotType type;
4 private SpotStatus status;
5 private Vehicle currentVehicle;
6 private final int floor;
7
8 public ParkingSpot(String spotId, SpotType type, int floor) {
9 this.spotId = spotId;
10 this.type = type;
11 this.floor = floor;
12 this.status = SpotStatus.AVAILABLE;
13 }
14
15 public synchronized boolean parkVehicle(Vehicle vehicle) {
16 if (status != SpotStatus.AVAILABLE) {
17 return false;
18 }
19
20 if (!vehicle.canFitInSpot(this)) {
21 return false;
22 }
23
24 this.currentVehicle = vehicle;
25 this.status = SpotStatus.OCCUPIED;
26 return true;
27 }
28
29 public synchronized void removeVehicle() {
30 this.currentVehicle = null;
31 this.status = SpotStatus.AVAILABLE;
32 }
33
34 public boolean isAvailable() {
35 return status == SpotStatus.AVAILABLE;
36 }
37
38 // Getters
39 public String getSpotId() { return spotId; }
40 public SpotType getType() { return type; }
41 public SpotStatus getStatus() { return status; }
42 public Vehicle getCurrentVehicle() { return currentVehicle; }
43 public int getFloor() { return floor; }
44}Parking Floor
java
1public class ParkingFloor {
2 private final int floorNumber;
3 private final Map<SpotType, List<ParkingSpot>> spotsByType;
4
5 public ParkingFloor(int floorNumber) {
6 this.floorNumber = floorNumber;
7 this.spotsByType = new HashMap<>();
8 for (SpotType type : SpotType.values()) {
9 spotsByType.put(type, new ArrayList<>());
10 }
11 }
12
13 public void addParkingSpot(ParkingSpot spot) {
14 spotsByType.get(spot.getType()).add(spot);
15 }
16
17 public ParkingSpot findAvailableSpot(Vehicle vehicle) {
18 // Try to find optimal spot first
19 SpotType preferredType = getPreferredSpotType(vehicle.getType());
20
21 for (ParkingSpot spot : spotsByType.get(preferredType)) {
22 if (spot.isAvailable() && vehicle.canFitInSpot(spot)) {
23 return spot;
24 }
25 }
26
27 // If preferred not available, try other compatible spots
28 for (List<ParkingSpot> spots : spotsByType.values()) {
29 for (ParkingSpot spot : spots) {
30 if (spot.isAvailable() && vehicle.canFitInSpot(spot)) {
31 return spot;
32 }
33 }
34 }
35
36 return null;
37 }
38
39 private SpotType getPreferredSpotType(VehicleType vehicleType) {
40 switch (vehicleType) {
41 case MOTORCYCLE:
42 return SpotType.COMPACT;
43 case CAR:
44 return SpotType.REGULAR;
45 case TRUCK:
46 return SpotType.LARGE;
47 default:
48 return SpotType.REGULAR;
49 }
50 }
51
52 public int getAvailableSpots(SpotType type) {
53 return (int) spotsByType.get(type).stream()
54 .filter(ParkingSpot::isAvailable)
55 .count();
56 }
57
58 public int getFloorNumber() {
59 return floorNumber;
60 }
61}Parking Ticket
java
1public class ParkingTicket {
2 private final String ticketId;
3 private final String licensePlate;
4 private final LocalDateTime entryTime;
5 private LocalDateTime exitTime;
6 private final ParkingSpot spot;
7 private double fee;
8 private boolean isPaid;
9
10 public ParkingTicket(String licensePlate, ParkingSpot spot) {
11 this.ticketId = generateTicketId();
12 this.licensePlate = licensePlate;
13 this.spot = spot;
14 this.entryTime = LocalDateTime.now();
15 this.isPaid = false;
16 }
17
18 private String generateTicketId() {
19 return "TKT-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
20 }
21
22 public void markExit() {
23 this.exitTime = LocalDateTime.now();
24 }
25
26 public long getParkingDurationInMinutes() {
27 LocalDateTime end = exitTime != null ? exitTime : LocalDateTime.now();
28 return ChronoUnit.MINUTES.between(entryTime, end);
29 }
30
31 // Getters and setters
32 public String getTicketId() { return ticketId; }
33 public String getLicensePlate() { return licensePlate; }
34 public LocalDateTime getEntryTime() { return entryTime; }
35 public ParkingSpot getSpot() { return spot; }
36 public double getFee() { return fee; }
37 public void setFee(double fee) { this.fee = fee; }
38 public boolean isPaid() { return isPaid; }
39 public void setPaid(boolean paid) { isPaid = paid; }
40}Fee Calculator (Strategy Pattern)
java
1public interface FeeCalculationStrategy {
2 double calculateFee(ParkingTicket ticket);
3}
4
5public class HourlyFeeStrategy implements FeeCalculationStrategy {
6 private static final double RATE_PER_HOUR = 5.0;
7 private static final double FIRST_HOUR_FREE = 1.0;
8
9 @Override
10 public double calculateFee(ParkingTicket ticket) {
11 long minutes = ticket.getParkingDurationInMinutes();
12 double hours = Math.ceil(minutes / 60.0);
13
14 if (hours <= FIRST_HOUR_FREE) {
15 return 0.0;
16 }
17
18 return (hours - FIRST_HOUR_FREE) * RATE_PER_HOUR;
19 }
20}
21
22public class FlatFeeStrategy implements FeeCalculationStrategy {
23 private static final double FLAT_FEE = 10.0;
24
25 @Override
26 public double calculateFee(ParkingTicket ticket) {
27 return FLAT_FEE;
28 }
29}Parking Lot (Singleton)
java
1public class ParkingLot {
2 private static ParkingLot instance;
3 private final String name;
4 private final List<ParkingFloor> floors;
5 private final Map<String, ParkingTicket> activeTickets;
6 private FeeCalculationStrategy feeStrategy;
7
8 private ParkingLot(String name, int numFloors) {
9 this.name = name;
10 this.floors = new ArrayList<>();
11 this.activeTickets = new ConcurrentHashMap<>();
12 this.feeStrategy = new HourlyFeeStrategy();
13
14 // Initialize floors
15 for (int i = 0; i < numFloors; i++) {
16 floors.add(new ParkingFloor(i));
17 }
18 }
19
20 public static synchronized ParkingLot getInstance(String name, int numFloors) {
21 if (instance == null) {
22 instance = new ParkingLot(name, numFloors);
23 }
24 return instance;
25 }
26
27 public ParkingTicket parkVehicle(Vehicle vehicle) {
28 // Find available spot across all floors
29 for (ParkingFloor floor : floors) {
30 ParkingSpot spot = floor.findAvailableSpot(vehicle);
31 if (spot != null && spot.parkVehicle(vehicle)) {
32 ParkingTicket ticket = new ParkingTicket(
33 vehicle.getLicensePlate(),
34 spot
35 );
36 activeTickets.put(ticket.getTicketId(), ticket);
37 return ticket;
38 }
39 }
40
41 return null; // No spot available
42 }
43
44 public double unparkVehicle(String ticketId) {
45 ParkingTicket ticket = activeTickets.get(ticketId);
46 if (ticket == null) {
47 throw new IllegalArgumentException("Invalid ticket");
48 }
49
50 ticket.markExit();
51 double fee = feeStrategy.calculateFee(ticket);
52 ticket.setFee(fee);
53
54 // Remove vehicle from spot
55 ticket.getSpot().removeVehicle();
56
57 // Remove ticket from active tickets
58 activeTickets.remove(ticketId);
59
60 return fee;
61 }
62
63 public void displayAvailability() {
64 System.out.println("=== " + name + " Availability ===");
65 for (ParkingFloor floor : floors) {
66 System.out.println("Floor " + floor.getFloorNumber() + ":");
67 for (SpotType type : SpotType.values()) {
68 int available = floor.getAvailableSpots(type);
69 System.out.println(" " + type + ": " + available + " spots");
70 }
71 }
72 }
73
74 public void setFeeStrategy(FeeCalculationStrategy strategy) {
75 this.feeStrategy = strategy;
76 }
77
78 public void addParkingSpot(int floorNumber, ParkingSpot spot) {
79 if (floorNumber < floors.size()) {
80 floors.get(floorNumber).addParkingSpot(spot);
81 }
82 }
83}Usage Example
java
1public class ParkingLotDemo {
2 public static void main(String[] args) {
3 // Initialize parking lot
4 ParkingLot parkingLot = ParkingLot.getInstance("City Center Parking", 3);
5
6 // Add parking spots
7 for (int floor = 0; floor < 3; floor++) {
8 for (int i = 0; i < 10; i++) {
9 parkingLot.addParkingSpot(floor,
10 new ParkingSpot("F" + floor + "-C" + i, SpotType.COMPACT, floor));
11 }
12 for (int i = 0; i < 20; i++) {
13 parkingLot.addParkingSpot(floor,
14 new ParkingSpot("F" + floor + "-R" + i, SpotType.REGULAR, floor));
15 }
16 for (int i = 0; i < 5; i++) {
17 parkingLot.addParkingSpot(floor,
18 new ParkingSpot("F" + floor + "-L" + i, SpotType.LARGE, floor));
19 }
20 }
21
22 // Display initial availability
23 parkingLot.displayAvailability();
24
25 // Park vehicles
26 Vehicle car1 = new Car("ABC-123");
27 ParkingTicket ticket1 = parkingLot.parkVehicle(car1);
28 System.out.println("Car parked. Ticket: " + ticket1.getTicketId());
29
30 Vehicle motorcycle = new Motorcycle("XYZ-789");
31 ParkingTicket ticket2 = parkingLot.parkVehicle(motorcycle);
32 System.out.println("Motorcycle parked. Ticket: " + ticket2.getTicketId());
33
34 // Display updated availability
35 parkingLot.displayAvailability();
36
37 // Simulate some time passing
38 try { Thread.sleep(2000); } catch (InterruptedException e) {}
39
40 // Unpark vehicle
41 double fee = parkingLot.unparkVehicle(ticket1.getTicketId());
42 System.out.println("Car unparked. Fee: $" + fee);
43
44 // Display final availability
45 parkingLot.displayAvailability();
46 }
47}Key Design Decisions
1. Thread Safety
- Used methods for parking/unparkingtext
1synchronized - for active ticketstext
1ConcurrentHashMap - Prevents race conditions during spot allocation
2. Extensibility
- Abstract class allows new vehicle typestext
1Vehicle - Strategy pattern for fee calculation
- Factory pattern for creating vehicles/spots
3. Optimization
- Spots grouped by type for faster lookups
- Preferred spot allocation (compact for motorcycle)
- O(1) ticket lookup using HashMap
Trade-offs
| Decision | Pros | Cons |
|---|---|---|
| Singleton ParkingLot | Single source of truth | Difficult to test, global state |
| Synchronized methods | Thread-safe | Performance bottleneck under high load |
| In-memory tickets | Fast access | Lost on restart (needs persistence) |
| Spot allocation strategy | Optimal space usage | May not always find best spot |
💡 Interview Tips & Out-of-the-Box Thinking
Common Pitfalls
- Not handling concurrent access: Multiple gates trying to assign same spot - must use synchronization or locks
- Forgetting vehicle size compatibility: Truck can't fit in compact spot - need validation
- Hardcoding spot allocation: Should be strategy pattern for different algorithms (nearest, cheapest, etc.)
- Not considering scalability: Single lock on parking lot won't scale - need per-floor or per-spot locking
Design Pattern Recognition
- Singleton: ParkingLot instance (one per physical location)
- Factory Pattern: Create different vehicle types
- Strategy Pattern: Different fee calculation strategies (hourly, daily, flat rate)
- Observer Pattern: Notify display boards when availability changes
- State Pattern: Vehicle spot status (Available → Reserved → Occupied)
Advanced Considerations
- Fine-grained locking: Lock per floor instead of entire parking lot (reduces contention)
- Optimistic locking: Use version numbers for spot assignment to handle race conditions
- Spot reservation expiry: Reserved spots should auto-release if vehicle doesn't arrive in 15 min
- Dynamic pricing: Charge more for spots closer to entrance or during peak hours
- Handicapped spots compliance: Legal requirement to reserve certain percentage
Creative Solutions
- Smart parking guidance: Use sensors and LED indicators to guide drivers to available spots
- Predictive availability: ML to predict when spots will free up based on historical data
- Valet mode: Allow valet parking where staff can park tighter/stack vehicles
- EV charging integration: Reserve spots with charging stations for electric vehicles
- Multi-entrance optimization: Route vehicles to floor with most availability
Trade-off Discussions
- HashMap vs Database: In-memory (fast, volatile) vs Persistent storage (slower, durable)
- Synchronized vs Lock: Method-level sync (simple) vs ReentrantLock (fine control)
- First-available vs Optimal: Quick allocation vs Best spot near entrance (more compute)
- Pre-allocation vs On-demand: Reserve spots vs Allocate when vehicle enters
Edge Cases to Mention
- Spot released but ticket exists: Payment processed but spot was reassigned due to bug (Answer: Transaction log for reconciliation)
- Vehicle parked in wrong spot type: Motorcycle in large spot - waste of space (Answer: Policy decision + pricing penalty)
- Ticket lost by customer: Can't identify which spot (Answer: License plate lookup + ID verification)
- Multiple entrances simultaneously: Same spot assigned to two vehicles (Answer: Distributed lock with TTL)
- Payment failure but gate opened: Vehicle exits without paying (Answer: License plate capture + billing later)
- Fire evacuation: All gates must open immediately (Answer: Emergency override mode)
Follow-up Enhancements
- Persistence: Add database layer for tickets and spots
- Payment Integration: Support multiple payment methods
- Reservation System: Allow advance booking
- Analytics: Track usage patterns, revenue
- Multi-level Locking: Fine-grained locks per floor
- Event-driven: Use events for availability updates
Class Diagram
Parking Lot Class Diagram
Detailed class/schema diagram will be displayed here