System Design

Parking Lot

20 min

Parking Lot

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

  1. Multiple floors with multiple parking spots
  2. Different spot sizes: Compact, Regular, Large
  3. Different vehicle types: Motorcycle, Car, Truck
  4. Entry/exit gates with ticket generation
  5. Parking fee calculation based on duration
  6. Display available spots at entrance
  7. Support for reserved/handicapped spots

Non-Functional Requirements

  1. Thread-safe for concurrent access
  2. Extensible for new vehicle types
  3. Follow SOLID principles
  4. 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

  1. Singleton Pattern: ParkingLot instance
  2. Factory Pattern: Vehicle and ParkingSpot creation
  3. Strategy Pattern: Parking fee calculation
  4. 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
    text
    1synchronized
    methods for parking/unparking
  • text
    1ConcurrentHashMap
    for active tickets
  • Prevents race conditions during spot allocation

2. Extensibility

  • Abstract
    text
    1Vehicle
    class allows new vehicle types
  • 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

DecisionProsCons
Singleton ParkingLotSingle source of truthDifficult to test, global state
Synchronized methodsThread-safePerformance bottleneck under high load
In-memory ticketsFast accessLost on restart (needs persistence)
Spot allocation strategyOptimal space usageMay 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

  1. Persistence: Add database layer for tickets and spots
  2. Payment Integration: Support multiple payment methods
  3. Reservation System: Allow advance booking
  4. Analytics: Track usage patterns, revenue
  5. Multi-level Locking: Fine-grained locks per floor
  6. Event-driven: Use events for availability updates

Class Diagram

Parking Lot Class Diagram

Detailed class/schema diagram will be displayed here

Press j for next, k for previous