LogoMasst Docs

Parking Lot System Design

Design a parking lot management system (LLD focused).

Problem Statement

Design an object-oriented parking lot system that manages vehicle parking, pricing, and availability.


Requirements

Functional Requirements

  • Multiple floors with different spot types
  • Vehicle types: motorcycle, car, bus/truck
  • Track available spots
  • Entry/exit with ticketing
  • Calculate parking fees
  • Support multiple entry/exit points

Non-Functional Requirements

  • Handle concurrent entries/exits
  • Real-time availability
  • Fault tolerance

Object-Oriented Design

Class Diagram

┌─────────────────────────────────────────────────────────────────────┐
│                        ParkingLot (Singleton)                        │
├─────────────────────────────────────────────────────────────────────┤
│  - id: String                                                        │
│  - floors: List<ParkingFloor>                                       │
│  - entryPanels: List<EntryPanel>                                    │
│  - exitPanels: List<ExitPanel>                                      │
├─────────────────────────────────────────────────────────────────────┤
│  + getAvailableSpots(VehicleType): int                              │
│  + parkVehicle(Vehicle): ParkingTicket                              │
│  + unparkVehicle(ParkingTicket): Payment                            │
└─────────────────────────────────────────────────────────────────────┘

                              │ has many

┌─────────────────────────────────────────────────────────────────────┐
│                         ParkingFloor                                 │
├─────────────────────────────────────────────────────────────────────┤
│  - floorNumber: int                                                 │
│  - spots: Map<SpotType, List<ParkingSpot>>                         │
│  - displayBoard: DisplayBoard                                       │
├─────────────────────────────────────────────────────────────────────┤
│  + getAvailableSpot(VehicleType): ParkingSpot                       │
│  + updateDisplayBoard(): void                                       │
└─────────────────────────────────────────────────────────────────────┘

                              │ has many

┌─────────────────────────────────────────────────────────────────────┐
│                         ParkingSpot                                  │
├─────────────────────────────────────────────────────────────────────┤
│  - spotId: String                                                   │
│  - spotType: SpotType                                               │
│  - isOccupied: boolean                                              │
│  - vehicle: Vehicle                                                 │
├─────────────────────────────────────────────────────────────────────┤
│  + assignVehicle(Vehicle): boolean                                  │
│  + removeVehicle(): Vehicle                                         │
│  + canFitVehicle(VehicleType): boolean                              │
└─────────────────────────────────────────────────────────────────────┘

Core Enums

enum VehicleType {
    MOTORCYCLE,
    CAR,
    BUS
}

enum SpotType {
    MOTORCYCLE,  // Small
    COMPACT,     // Small + Car
    LARGE,       // Small + Car + Bus
    HANDICAPPED,
    ELECTRIC
}

enum TicketStatus {
    ACTIVE,
    PAID,
    LOST
}

enum PaymentStatus {
    PENDING,
    COMPLETED,
    FAILED,
    REFUNDED
}

Core Classes

Vehicle (Abstract)

public abstract class Vehicle {
    private String licensePlate;
    private VehicleType type;
    private ParkingTicket ticket;

    public abstract VehicleType getType();
}

public class Car extends Vehicle {
    @Override
    public VehicleType getType() {
        return VehicleType.CAR;
    }
}

public class Motorcycle extends Vehicle {
    @Override
    public VehicleType getType() {
        return VehicleType.MOTORCYCLE;
    }
}

public class Bus extends Vehicle {
    @Override
    public VehicleType getType() {
        return VehicleType.BUS;
    }
}

ParkingSpot

public class ParkingSpot {
    private String spotId;
    private SpotType spotType;
    private boolean isOccupied;
    private Vehicle vehicle;

    public boolean canFitVehicle(VehicleType vehicleType) {
        switch (vehicleType) {
            case MOTORCYCLE:
                return true; // Fits any spot
            case CAR:
                return spotType != SpotType.MOTORCYCLE;
            case BUS:
                return spotType == SpotType.LARGE;
            default:
                return false;
        }
    }

    public synchronized boolean assignVehicle(Vehicle vehicle) {
        if (isOccupied || !canFitVehicle(vehicle.getType())) {
            return false;
        }
        this.vehicle = vehicle;
        this.isOccupied = true;
        return true;
    }

    public synchronized Vehicle removeVehicle() {
        Vehicle v = this.vehicle;
        this.vehicle = null;
        this.isOccupied = false;
        return v;
    }
}

ParkingTicket

public class ParkingTicket {
    private String ticketId;
    private String licensePlate;
    private ParkingSpot spot;
    private LocalDateTime entryTime;
    private LocalDateTime exitTime;
    private TicketStatus status;

    public ParkingTicket(Vehicle vehicle, ParkingSpot spot) {
        this.ticketId = generateTicketId();
        this.licensePlate = vehicle.getLicensePlate();
        this.spot = spot;
        this.entryTime = LocalDateTime.now();
        this.status = TicketStatus.ACTIVE;
    }

    public Duration getParkingDuration() {
        LocalDateTime end = exitTime != null ? exitTime : LocalDateTime.now();
        return Duration.between(entryTime, end);
    }
}

Pricing Strategy (Strategy Pattern)

public interface PricingStrategy {
    double calculatePrice(ParkingTicket ticket);
}

public class HourlyPricing implements PricingStrategy {
    private Map<VehicleType, Double> hourlyRates;

    public HourlyPricing() {
        hourlyRates = new HashMap<>();
        hourlyRates.put(VehicleType.MOTORCYCLE, 1.0);
        hourlyRates.put(VehicleType.CAR, 2.0);
        hourlyRates.put(VehicleType.BUS, 5.0);
    }

    @Override
    public double calculatePrice(ParkingTicket ticket) {
        long hours = ticket.getParkingDuration().toHours();
        if (hours == 0) hours = 1; // Minimum 1 hour

        VehicleType type = ticket.getSpot().getVehicle().getType();
        return hours * hourlyRates.get(type);
    }
}

public class FlatRatePricing implements PricingStrategy {
    private double flatRate;

    @Override
    public double calculatePrice(ParkingTicket ticket) {
        return flatRate;
    }
}

public class WeekendPricing implements PricingStrategy {
    private PricingStrategy weekdayStrategy;
    private double weekendMultiplier;

    @Override
    public double calculatePrice(ParkingTicket ticket) {
        double basePrice = weekdayStrategy.calculatePrice(ticket);
        if (isWeekend(ticket.getEntryTime())) {
            return basePrice * weekendMultiplier;
        }
        return basePrice;
    }
}

ParkingLot (Singleton)

public class ParkingLot {
    private static ParkingLot instance;
    private String id;
    private String name;
    private List<ParkingFloor> floors;
    private List<EntryPanel> entryPanels;
    private List<ExitPanel> exitPanels;
    private PricingStrategy pricingStrategy;

    private ParkingLot() {
        floors = new ArrayList<>();
        entryPanels = new ArrayList<>();
        exitPanels = new ArrayList<>();
        pricingStrategy = new HourlyPricing();
    }

    public static synchronized ParkingLot getInstance() {
        if (instance == null) {
            instance = new ParkingLot();
        }
        return instance;
    }

    public ParkingTicket parkVehicle(Vehicle vehicle) {
        ParkingSpot spot = findAvailableSpot(vehicle.getType());
        if (spot == null) {
            throw new ParkingFullException("No spot available");
        }

        spot.assignVehicle(vehicle);
        ParkingTicket ticket = new ParkingTicket(vehicle, spot);
        updateDisplayBoards();
        return ticket;
    }

    public Payment unparkVehicle(ParkingTicket ticket) {
        ticket.setExitTime(LocalDateTime.now());
        double amount = pricingStrategy.calculatePrice(ticket);

        ticket.getSpot().removeVehicle();
        ticket.setStatus(TicketStatus.PAID);
        updateDisplayBoards();

        return new Payment(amount, ticket);
    }

    private ParkingSpot findAvailableSpot(VehicleType type) {
        for (ParkingFloor floor : floors) {
            ParkingSpot spot = floor.getAvailableSpot(type);
            if (spot != null) {
                return spot;
            }
        }
        return null;
    }

    public int getAvailableSpots(VehicleType type) {
        return floors.stream()
            .mapToInt(f -> f.getAvailableSpotsCount(type))
            .sum();
    }
}

Entry/Exit Panels

public class EntryPanel {
    private String panelId;
    private ParkingLot parkingLot;

    public ParkingTicket processEntry(Vehicle vehicle) {
        if (parkingLot.getAvailableSpots(vehicle.getType()) == 0) {
            displayFull();
            return null;
        }

        ParkingTicket ticket = parkingLot.parkVehicle(vehicle);
        printTicket(ticket);
        openGate();
        return ticket;
    }
}

public class ExitPanel {
    private String panelId;
    private ParkingLot parkingLot;
    private PaymentProcessor paymentProcessor;

    public void processExit(ParkingTicket ticket) {
        Payment payment = parkingLot.unparkVehicle(ticket);
        displayAmount(payment.getAmount());

        if (paymentProcessor.processPayment(payment)) {
            openGate();
        } else {
            displayPaymentError();
        }
    }
}

Display Board

public class DisplayBoard {
    private String boardId;
    private Map<SpotType, Integer> availableSpots;

    public void update(Map<SpotType, Integer> counts) {
        this.availableSpots = counts;
        render();
    }

    private void render() {
        System.out.println("=== Available Spots ===");
        for (Map.Entry<SpotType, Integer> entry : availableSpots.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
    }
}

Concurrency Handling

public class ConcurrentParkingSpot {
    private final ReentrantLock lock = new ReentrantLock();
    private volatile boolean isOccupied;
    private Vehicle vehicle;

    public boolean assignVehicle(Vehicle vehicle) {
        lock.lock();
        try {
            if (isOccupied) {
                return false;
            }
            this.vehicle = vehicle;
            this.isOccupied = true;
            return true;
        } finally {
            lock.unlock();
        }
    }
}

// Or using optimistic locking with database
@Entity
public class ParkingSpotEntity {
    @Id
    private String spotId;

    @Version
    private Long version;  // Optimistic locking

    private boolean isOccupied;
}

API Design (for System Integration)

POST /api/v1/parking/entry
Request: { "licensePlate": "ABC123", "vehicleType": "CAR" }
Response: { "ticketId": "TKT-001", "spotId": "F1-A-001", "entryTime": "..." }

POST /api/v1/parking/exit
Request: { "ticketId": "TKT-001" }
Response: { "amount": 10.00, "duration": "2h 30m", "paymentUrl": "..." }

GET /api/v1/parking/availability
Response: {
  "floors": [
    { "floor": 1, "motorcycle": 10, "compact": 25, "large": 5 },
    { "floor": 2, "motorcycle": 8, "compact": 30, "large": 3 }
  ]
}

Design Patterns Used

PatternUsage
SingletonParkingLot instance
FactoryVehicle creation
StrategyPricing calculation
ObserverDisplay board updates
StateTicket status management

Interview Tips

  • Start with requirements clarification
  • Draw class diagram first
  • Discuss concurrency handling
  • Explain Strategy pattern for pricing
  • Cover edge cases: full lot, lost ticket
  • Mention extension points