LLD Problem

Design a Parking Lot System

A classic object-oriented design problem covering classes, relationships, and extensibility.

1Problem Statement

Design a parking lot system that can:
  • Handle multiple floors with different spot sizes
  • Support different vehicle types (motorcycle, car, bus/truck)
  • Track available spots and assign vehicles efficiently
  • Calculate parking fees based on duration
  • Handle entry/exit with ticket generation

2Requirements

Functional Requirements

  • Park and unpark vehicles
  • Track spot availability per floor
  • Different spot sizes for different vehicles
  • Generate parking tickets on entry
  • Calculate fees on exit
  • Display available spots count

Non-Functional Requirements

  • Handle concurrent entry/exit
  • Efficient spot allocation
  • Extensible for new vehicle types
  • Support multiple payment methods
  • Real-time availability updates

Clarifying Questions

Q: How many floors and spots?
A: 5 floors, 100 spots per floor (500 total)
Q: Vehicle types?
A: Motorcycle, Car, Bus/Truck - each needs different spot sizes
Q: Pricing model?
A: Hourly rate, different for each vehicle type
Q: Multiple entry/exit points?
A: Yes, one entry and one exit per floor

3Core Entities

EntityKey AttributesRelationships
ParkingLotname, floors[], entryPanels[], exitPanels[]1-to-Many Floors
ParkingFloorfloorNumber, spots[], displayBoard1-to-Many ParkingSpots
ParkingSpotspotNumber, spotType, vehicle, isAvailableMany-to-1 Floor, 0-1 Vehicle
VehiclelicensePlate, vehicleType, color0-1 ParkingSpot
ParkingTicketticketNumber, entryTime, exitTime, vehicle, spot1-to-1 Vehicle, 1-to-1 Spot
Paymentamount, paymentTime, method, status1-to-1 Ticket

4Class Diagram

Domain Model


          ┌────────────────────────────────────────────┐
          │         ParkingLot (Singleton)             │
          ├────────────────────────────────────────────┤
          │ - instance: ParkingLot                     │
          │ - name: String                             │
          │ - floors: List<ParkingFloor>               │
          │ - tickets: Map<String, ParkingTicket>      │
          ├────────────────────────────────────────────┤
          │ + getInstance(): ParkingLot                │
          │ + addFloor(floor): void                    │
          │ + parkVehicle(vehicle): ParkingTicket      │
          │ + unparkVehicle(ticket): Payment           │
          │ + getAvailableSpots(type): int             │
          └──────────────────┬─────────────────────────┘
                             │
                             │ has many
                             ▼
          ┌────────────────────────────────────────────┐
          │              ParkingFloor                  │
          ├────────────────────────────────────────────┤
          │ - floorNumber: int                         │
          │ - spots: Map<SpotType, List<ParkingSpot>>  │
          ├────────────────────────────────────────────┤
          │ + getAvailableSpots(type): int             │
          │ + findAvailableSpot(vehicleType): Spot     │
          │ + updateDisplayBoard(): void               │
          └──────────────────┬─────────────────────────┘
                             │
                             │ has many
                             ▼
          ┌────────────────────────────────────────────┐
          │         <<abstract>> ParkingSpot           │
          ├────────────────────────────────────────────┤
          │ - spotNumber: String                       │
          │ - isAvailable: boolean                     │
          │ - vehicle: Vehicle                         │
          ├────────────────────────────────────────────┤
          │ + assignVehicle(v): boolean                │
          │ + removeVehicle(): Vehicle                 │
          │ + canFitVehicle(v): boolean                │
          └────────────────────────────────────────────┘
                    ▲              ▲              ▲
         ┌──────────┘              │              └──────────┐
         │                         │                         │
┌────────┴───────┐    ┌────────────┴────────┐    ┌──────────┴───────┐
│ MotorcycleSpot │    │    CompactSpot      │    │    LargeSpot     │
└────────────────┘    └─────────────────────┘    └──────────────────┘

Vehicle Hierarchy


          ┌────────────────────────────────────────────┐
          │           <<abstract>> Vehicle             │
          ├────────────────────────────────────────────┤
          │ - licensePlate: String                     │
          │ - vehicleType: VehicleType                 │
          │ - color: String                            │
          ├────────────────────────────────────────────┤
          │ + getType(): VehicleType                   │
          └────────────────────────────────────────────┘
                    ▲              ▲              ▲
         ┌──────────┘              │              └──────────┐
         │                         │                         │
┌────────┴───────┐    ┌────────────┴────────┐    ┌──────────┴───────┐
│   Motorcycle   │    │        Car          │    │    Bus/Truck     │
└────────────────┘    └─────────────────────┘    └──────────────────┘

Strategy Pattern for Pricing


            ┌──────────────────────────────────────────┐
            │    <<interface>> PricingStrategy         │
            ├──────────────────────────────────────────┤
            │ + calculateFee(ticket): double           │
            └──────────────────────────────────────────┘
                             ▲
           ┌─────────────────┼─────────────────┐
           │                 │                 │
    ┌──────┴──────┐   ┌──────┴──────┐   ┌──────┴──────┐
    │HourlyPricing│   │ FlatPricing │   │WeekendPricing│
    ├─────────────┤   ├─────────────┤   ├─────────────┤
    │ rate/hour   │   │ fixed rate  │   │ special rate│
    └─────────────┘   └─────────────┘   └─────────────┘

5Key Flows

1. Vehicle Entry Flow

1
Vehicle Arrives
Vehicle arrives at entry panel, scans or enters details
2
Check Availability
System checks if spots available for vehicle type
Decision
Spot AvailableShow Full Message
3
Find Best Spot
Find nearest available spot on lowest floor
4
Assign Spot
Mark spot as occupied, assign vehicle to spot
5
Generate Ticket
Create ticket with entry time, spot, vehicle info
6
Open Gate
Print/display ticket, open entry gate

2. Vehicle Exit Flow

1
Scan Ticket
Vehicle scans ticket at exit panel
2
Calculate Fee
Get entry time, calculate duration, apply rate
3
Process Payment
Accept payment via card/cash/UPI
Decision
Payment SuccessRetry Payment
4
Free Spot
Mark spot as available, remove vehicle reference
5
Update Display
Increment available count on display board
6
Open Gate
Print receipt, open exit gate

3. Find Available Spot Algorithm

1
Get Vehicle Type
Motorcycle, Car, or Bus/Truck
2
Get Spot Priority
Motorcycle: [MOTORCYCLE, COMPACT, LARGE], Car: [COMPACT, LARGE], Bus: [LARGE]
3
Iterate Floors
Start from floor 1 (bottom) to optimize walking distance
4
Check Spot Types
For each priority, check if available spot exists
5
Return First Match
Return first available spot, or null if lot is full

6Algorithms

Finding Available Spot

Strategy: Find smallest spot that fits, on lowest floor

Steps:

  1. Get spot priority list based on vehicle type
  2. Iterate through floors from bottom to top
  3. For each floor, check spot types in priority order
  4. Return first available spot found
  5. If no spot found on any floor, return null (lot full)

Complexity: O(floors × spotTypes) = O(F × S)

Optimization: Maintain count of available spots per type per floor

Fee Calculation

Strategy Pattern: Different pricing strategies for flexibility

Hourly Pricing:

  1. Calculate duration = exitTime - entryTime
  2. Convert to hours (round up partial hours)
  3. Get hourly rate based on vehicle type
  4. Fee = hours × hourlyRate

Sample Rates:

Motorcycle: $10/hr
Car: $20/hr
Bus: $50/hr

7Design Patterns

Singleton Pattern

Where: ParkingLot class

Why: Only one instance should manage all floors and spots. Centralized resource management.

getInstance()private constructor

Factory Pattern

Where: Creating vehicles and spots

Why: Encapsulates object creation logic. Easy to add new vehicle/spot types.

VehicleFactorySpotFactory

Strategy Pattern

Where: PricingStrategy for fee calculation

Why: Allows switching pricing models without changing core logic.

HourlyPricingFlatPricingWeekendPricing

Observer Pattern

Where: DisplayBoard updates

Why: Decouples spot changes from display updates. Real-time notifications.

ObservableDisplayBoard

Abstract Factory

Where: Creating related objects

Why: Create families of related objects (spots and their displays).

SpotFactoryDisplayFactory

State Pattern

Where: Ticket status management

Why: Handle ticket state transitions (ACTIVE → PAID → EXITED).

TicketStateActiveStatePaidState

8Enums

VehicleType

MOTORCYCLECARBUSTRUCK

SpotType

MOTORCYCLECOMPACTLARGEHANDICAPPEDELECTRIC

TicketStatus

ACTIVEPAIDEXITEDLOST

PaymentStatus

PENDINGCOMPLETEDFAILEDREFUNDED

9Java Implementation

Enumsjava
public enum VehicleType {
    MOTORCYCLE, CAR, BUS, TRUCK
}

public enum SpotType {
    MOTORCYCLE, COMPACT, LARGE, HANDICAPPED, ELECTRIC
}

public enum TicketStatus {
    ACTIVE, PAID, EXITED, LOST
}
Abstract Vehicle Classjava
public abstract class Vehicle {
    private String licensePlate;
    private VehicleType type;
    private String color;
    
    public Vehicle(String licensePlate, VehicleType type, String color) {
        this.licensePlate = licensePlate;
        this.type = type;
        this.color = color;
    }
    
    public VehicleType getType() { return type; }
    public String getLicensePlate() { return licensePlate; }
}

public class Car extends Vehicle {
    public Car(String licensePlate, String color) {
        super(licensePlate, VehicleType.CAR, color);
    }
}

public class Motorcycle extends Vehicle {
    public Motorcycle(String licensePlate, String color) {
        super(licensePlate, VehicleType.MOTORCYCLE, color);
    }
}

public class Bus extends Vehicle {
    public Bus(String licensePlate, String color) {
        super(licensePlate, VehicleType.BUS, color);
    }
}
Abstract ParkingSpot Classjava
public abstract class ParkingSpot {
    private String spotNumber;
    private SpotType spotType;
    private Vehicle vehicle;
    
    public ParkingSpot(String spotNumber, SpotType spotType) {
        this.spotNumber = spotNumber;
        this.spotType = spotType;
    }
    
    public boolean isAvailable() {
        return vehicle == null;
    }
    
    public boolean assignVehicle(Vehicle vehicle) {
        if (!isAvailable() || !canFitVehicle(vehicle)) {
            return false;
        }
        this.vehicle = vehicle;
        return true;
    }
    
    public Vehicle removeVehicle() {
        Vehicle v = this.vehicle;
        this.vehicle = null;
        return v;
    }
    
    public abstract boolean canFitVehicle(Vehicle vehicle);
    
    // Getters
    public String getSpotNumber() { return spotNumber; }
    public SpotType getSpotType() { return spotType; }
    public Vehicle getVehicle() { return vehicle; }
}

public class CompactSpot extends ParkingSpot {
    public CompactSpot(String spotNumber) {
        super(spotNumber, SpotType.COMPACT);
    }
    
    @Override
    public boolean canFitVehicle(Vehicle vehicle) {
        return vehicle.getType() == VehicleType.CAR || 
               vehicle.getType() == VehicleType.MOTORCYCLE;
    }
}

public class LargeSpot extends ParkingSpot {
    public LargeSpot(String spotNumber) {
        super(spotNumber, SpotType.LARGE);
    }
    
    @Override
    public boolean canFitVehicle(Vehicle vehicle) {
        return true; // Large spots can fit all vehicle types
    }
}

public class MotorcycleSpot extends ParkingSpot {
    public MotorcycleSpot(String spotNumber) {
        super(spotNumber, SpotType.MOTORCYCLE);
    }
    
    @Override
    public boolean canFitVehicle(Vehicle vehicle) {
        return vehicle.getType() == VehicleType.MOTORCYCLE;
    }
}
ParkingTicket Classjava
public class ParkingTicket {
    private String ticketNumber;
    private LocalDateTime entryTime;
    private LocalDateTime exitTime;
    private Vehicle vehicle;
    private ParkingSpot spot;
    private TicketStatus status;
    
    public ParkingTicket(String ticketNumber, Vehicle vehicle, ParkingSpot spot) {
        this.ticketNumber = ticketNumber;
        this.vehicle = vehicle;
        this.spot = spot;
        this.entryTime = LocalDateTime.now();
        this.status = TicketStatus.ACTIVE;
    }
    
    public void markExited() {
        this.exitTime = LocalDateTime.now();
        this.status = TicketStatus.EXITED;
    }
    
    public long getDurationInHours() {
        LocalDateTime end = exitTime != null ? exitTime : LocalDateTime.now();
        long minutes = ChronoUnit.MINUTES.between(entryTime, end);
        return (long) Math.ceil(minutes / 60.0); // Round up
    }
    
    // Getters
    public String getTicketNumber() { return ticketNumber; }
    public Vehicle getVehicle() { return vehicle; }
    public ParkingSpot getSpot() { return spot; }
    public TicketStatus getStatus() { return status; }
    public LocalDateTime getEntryTime() { return entryTime; }
}
ParkingFloor Classjava
public class ParkingFloor {
    private int floorNumber;
    private Map<SpotType, List<ParkingSpot>> spots;
    
    public ParkingFloor(int floorNumber) {
        this.floorNumber = floorNumber;
        this.spots = new EnumMap<>(SpotType.class);
        for (SpotType type : SpotType.values()) {
            spots.put(type, new ArrayList<>());
        }
    }
    
    public void addSpot(ParkingSpot spot) {
        spots.get(spot.getSpotType()).add(spot);
    }
    
    public ParkingSpot findAvailableSpot(VehicleType vehicleType) {
        List<SpotType> priority = getSpotPriority(vehicleType);
        
        for (SpotType spotType : priority) {
            List<ParkingSpot> spotList = spots.get(spotType);
            for (ParkingSpot spot : spotList) {
                if (spot.isAvailable()) {
                    return spot;
                }
            }
        }
        return null;
    }
    
    private List<SpotType> getSpotPriority(VehicleType vehicleType) {
        switch (vehicleType) {
            case MOTORCYCLE:
                return Arrays.asList(SpotType.MOTORCYCLE, SpotType.COMPACT, SpotType.LARGE);
            case CAR:
                return Arrays.asList(SpotType.COMPACT, SpotType.LARGE);
            case BUS:
            case TRUCK:
                return Arrays.asList(SpotType.LARGE);
            default:
                return Arrays.asList(SpotType.LARGE);
        }
    }
    
    public int getAvailableCount(SpotType spotType) {
        return (int) spots.get(spotType).stream()
            .filter(ParkingSpot::isAvailable)
            .count();
    }
    
    public int getFloorNumber() { return floorNumber; }
}
Pricing Strategyjava
public interface PricingStrategy {
    double calculateFee(ParkingTicket ticket);
}

public class HourlyPricingStrategy implements PricingStrategy {
    private Map<VehicleType, Double> hourlyRates;
    
    public HourlyPricingStrategy() {
        hourlyRates = new EnumMap<>(VehicleType.class);
        hourlyRates.put(VehicleType.MOTORCYCLE, 10.0);
        hourlyRates.put(VehicleType.CAR, 20.0);
        hourlyRates.put(VehicleType.BUS, 50.0);
        hourlyRates.put(VehicleType.TRUCK, 50.0);
    }
    
    @Override
    public double calculateFee(ParkingTicket ticket) {
        long hours = ticket.getDurationInHours();
        VehicleType type = ticket.getVehicle().getType();
        double rate = hourlyRates.getOrDefault(type, 20.0);
        return hours * rate;
    }
}

public class FlatRatePricingStrategy implements PricingStrategy {
    private double flatRate;
    
    public FlatRatePricingStrategy(double flatRate) {
        this.flatRate = flatRate;
    }
    
    @Override
    public double calculateFee(ParkingTicket ticket) {
        return flatRate;
    }
}
ParkingLot Singletonjava
public class ParkingLot {
    private static ParkingLot instance;
    
    private String name;
    private List<ParkingFloor> floors;
    private Map<String, ParkingTicket> tickets;
    private AtomicInteger ticketCounter;
    private PricingStrategy pricingStrategy;
    
    private ParkingLot(String name) {
        this.name = name;
        this.floors = new ArrayList<>();
        this.tickets = new ConcurrentHashMap<>();
        this.ticketCounter = new AtomicInteger(0);
        this.pricingStrategy = new HourlyPricingStrategy();
    }
    
    public static synchronized ParkingLot getInstance(String name) {
        if (instance == null) {
            instance = new ParkingLot(name);
        }
        return instance;
    }
    
    public static ParkingLot getInstance() {
        return getInstance("Default Parking Lot");
    }
    
    public void addFloor(ParkingFloor floor) {
        floors.add(floor);
    }
    
    public void setPricingStrategy(PricingStrategy strategy) {
        this.pricingStrategy = strategy;
    }
    
    public ParkingTicket parkVehicle(Vehicle vehicle) {
        // Find available spot
        for (ParkingFloor floor : floors) {
            ParkingSpot spot = floor.findAvailableSpot(vehicle.getType());
            if (spot != null && spot.assignVehicle(vehicle)) {
                // Create ticket
                String ticketNumber = "T-" + ticketCounter.incrementAndGet();
                ParkingTicket ticket = new ParkingTicket(ticketNumber, vehicle, spot);
                tickets.put(ticketNumber, ticket);
                return ticket;
            }
        }
        return null; // Parking lot is full
    }
    
    public double unparkVehicle(String ticketNumber) {
        ParkingTicket ticket = tickets.get(ticketNumber);
        if (ticket == null) {
            throw new IllegalArgumentException("Invalid ticket");
        }
        
        if (ticket.getStatus() == TicketStatus.EXITED) {
            throw new IllegalStateException("Ticket already used");
        }
        
        // Mark ticket as exited
        ticket.markExited();
        
        // Free the spot
        ticket.getSpot().removeVehicle();
        
        // Calculate and return fee
        return pricingStrategy.calculateFee(ticket);
    }
    
    public Map<SpotType, Integer> getAvailableSpots() {
        Map<SpotType, Integer> counts = new EnumMap<>(SpotType.class);
        for (SpotType type : SpotType.values()) {
            counts.put(type, 0);
        }
        
        for (ParkingFloor floor : floors) {
            for (SpotType type : SpotType.values()) {
                int current = counts.get(type);
                counts.put(type, current + floor.getAvailableCount(type));
            }
        }
        return counts;
    }
    
    public boolean isFull() {
        return getAvailableSpots().values().stream()
            .allMatch(count -> count == 0);
    }
}
Demo Usagejava
public class ParkingLotDemo {
    public static void main(String[] args) {
        // Get singleton instance
        ParkingLot lot = ParkingLot.getInstance("Downtown Parking");
        
        // Create floor with spots
        ParkingFloor floor1 = new ParkingFloor(1);
        for (int i = 1; i <= 5; i++) {
            floor1.addSpot(new MotorcycleSpot("1-M" + i));
        }
        for (int i = 1; i <= 20; i++) {
            floor1.addSpot(new CompactSpot("1-C" + i));
        }
        for (int i = 1; i <= 10; i++) {
            floor1.addSpot(new LargeSpot("1-L" + i));
        }
        lot.addFloor(floor1);
        
        // Display available spots
        System.out.println("Available spots: " + lot.getAvailableSpots());
        // Output: {MOTORCYCLE=5, COMPACT=20, LARGE=10, ...}
        
        // Park a car
        Vehicle car = new Car("ABC-123", "Red");
        ParkingTicket ticket = lot.parkVehicle(car);
        
        if (ticket != null) {
            System.out.println("Car parked at spot: " + ticket.getSpot().getSpotNumber());
            System.out.println("Ticket: " + ticket.getTicketNumber());
        }
        
        // Simulate some time passing...
        // Thread.sleep(3600000); // 1 hour
        
        // Unpark and pay
        double fee = lot.unparkVehicle(ticket.getTicketNumber());
        System.out.println("Parking fee: $" + fee);
        
        // Check spots again
        System.out.println("Available spots after exit: " + lot.getAvailableSpots());
    }
}

10Interview Follow-up Questions

Handle Concurrent Entry/Exit?

Use synchronized blocks for spot assignment, ConcurrentHashMap for tickets, AtomicInteger for ticket counter.

Electric Vehicle Support?

Add ElectricSpot with charging capability. Vehicle has 'needsCharging' flag. Prioritize electric spots for EVs.

Reservation System?

Add Reservation class with user, time slot, vehicle type. Check reservations before spot assignment.

Multi-entrance Support?

Each entrance has its own panel. All share the same ParkingLot singleton. Synchronize spot allocation.

Lost Ticket Handling?

Charge maximum daily rate. Verify vehicle via license plate scan. Manual override by attendant.

Peak Hour Pricing?

Implement TimeBasedPricingStrategy. Check current time in calculateFee(). Apply surge multiplier.

11Key Takeaways

1Singleton for ParkingLot ensures centralized management of all floors and spots
2Abstract classes for Vehicle and ParkingSpot allow easy extension for new types
3Strategy pattern for pricing makes it easy to add new pricing models (hourly, flat, weekend)
4Encapsulate spot finding in ParkingFloor - each floor knows how to find its spots
5ConcurrentHashMap and AtomicInteger for thread-safe operations in multi-entry scenarios

?Quiz

1. Why use Singleton for ParkingLot?

2. A motorcycle can park in which spots?

3. What pattern is used for fee calculation?