DEV Community

DevCorner2
DevCorner2

Posted on

🚗Designing a Scalable Parking Lot System — An LLD Interview Walkthrough

🎯 Objective

Design a modular and extensible Parking Lot System that supports:

  • Multiple parking floors
  • Vehicle types: Car, Bike, Truck
  • Parking slots of different sizes
  • Entry/Exit Gates
  • Ticket generation and payment handling
  • Real-time slot availability

We'll approach this LLD as if in a real interview, building the system iteratively, extracting entities, responsibilities, and applying OOP principles, SOLID, and design patterns for long-term maintainability.


đź§  Step 1: Understanding and Modeling the Domain

Let’s whiteboard core domain concepts through real-world storytelling:

“Imagine you pull into a mall with your car. At the entrance gate, the system detects your vehicle, assigns a slot, and prints a ticket. You later exit, scan the ticket, pay the amount based on duration, and leave.”

From this flow, we extract the key nouns → candidate entities:

🎯 Initial Entity Candidates:

  • Vehicle
  • ParkingSlot
  • ParkingFloor
  • ParkingLot
  • Gate (Entry/Exit)
  • Ticket
  • Payment
  • DisplayBoard (shows available slots)

🔍 Step 2: Define Core Abstractions

Let’s now define base classes/interfaces with clean responsibilities.


✅ 2.1 Vehicle — Entity

public abstract class Vehicle {
    private final String licensePlate;
    private final VehicleType type;

    public Vehicle(String licensePlate, VehicleType type) {
        this.licensePlate = licensePlate;
        this.type = type;
    }

    // Getters
}
Enter fullscreen mode Exit fullscreen mode

✅ 2.2 VehicleType — Enum

public enum VehicleType {
    CAR, BIKE, TRUCK
}
Enter fullscreen mode Exit fullscreen mode

✅ 2.3 ParkingSlot — Entity with SRP

public class ParkingSlot {
    private final String id;
    private final VehicleType supportedType;
    private boolean isOccupied;

    public ParkingSlot(String id, VehicleType supportedType) {
        this.id = id;
        this.supportedType = supportedType;
    }

    public boolean canFitVehicle(Vehicle vehicle) {
        return !isOccupied && supportedType == vehicle.getType();
    }

    public void assignVehicle() { isOccupied = true; }

    public void releaseVehicle() { isOccupied = false; }

    // Getters
}
Enter fullscreen mode Exit fullscreen mode

✅ 2.4 ParkingFloor — Aggregates Slots

public class ParkingFloor {
    private final String name;
    private final List<ParkingSlot> slots;

    public ParkingFloor(String name) {
        this.name = name;
        this.slots = new ArrayList<>();
    }

    public Optional<ParkingSlot> getAvailableSlot(Vehicle vehicle) {
        return slots.stream().filter(slot -> slot.canFitVehicle(vehicle)).findFirst();
    }

    public void addSlot(ParkingSlot slot) {
        slots.add(slot);
    }
}
Enter fullscreen mode Exit fullscreen mode

Extensibility Note: ParkingFloor doesn’t care about vehicle logic; it just aggregates and filters slots — single responsibility preserved.


✅ 2.5 ParkingLot — System Coordinator

public class ParkingLot {
    private final List<ParkingFloor> floors;
    private final List<Gate> entryGates;
    private final List<Gate> exitGates;

    public ParkingLot(List<ParkingFloor> floors, List<Gate> entryGates, List<Gate> exitGates) {
        this.floors = floors;
        this.entryGates = entryGates;
        this.exitGates = exitGates;
    }

    public Optional<ParkingSlot> findSlotForVehicle(Vehicle vehicle) {
        for (ParkingFloor floor : floors) {
            Optional<ParkingSlot> slot = floor.getAvailableSlot(vehicle);
            if (slot.isPresent()) return slot;
        }
        return Optional.empty();
    }
}
Enter fullscreen mode Exit fullscreen mode

🏷️ Step 3: Ticket Generation Logic

✅ 3.1 Ticket — Value Object

public class Ticket {
    private final String ticketId;
    private final Vehicle vehicle;
    private final ParkingSlot slot;
    private final LocalDateTime entryTime;

    public Ticket(String ticketId, Vehicle vehicle, ParkingSlot slot) {
        this.ticketId = ticketId;
        this.vehicle = vehicle;
        this.slot = slot;
        this.entryTime = LocalDateTime.now();
    }

    // Getters
}
Enter fullscreen mode Exit fullscreen mode

✅ 3.2 Gate — Factory-like Role

public abstract class Gate {
    protected final String gateId;
    public Gate(String gateId) { this.gateId = gateId; }
}

public class EntryGate extends Gate {
    private final ParkingLot lot;

    public EntryGate(String gateId, ParkingLot lot) {
        super(gateId);
        this.lot = lot;
    }

    public Optional<Ticket> generateTicket(Vehicle vehicle) {
        Optional<ParkingSlot> slotOpt = lot.findSlotForVehicle(vehicle);
        if (slotOpt.isEmpty()) return Optional.empty();

        ParkingSlot slot = slotOpt.get();
        slot.assignVehicle();
        return Optional.of(new Ticket(UUID.randomUUID().toString(), vehicle, slot));
    }
}
Enter fullscreen mode Exit fullscreen mode

đź’° Step 4: Payment and Exit Workflow

✅ 4.1 Payment — Entity

public class Payment {
    private final String paymentId;
    private final double amount;
    private final LocalDateTime paidAt;
    private final PaymentStatus status;

    public Payment(double amount) {
        this.paymentId = UUID.randomUUID().toString();
        this.amount = amount;
        this.paidAt = LocalDateTime.now();
        this.status = PaymentStatus.SUCCESS;
    }
}
Enter fullscreen mode Exit fullscreen mode

✅ 4.2 ExitGate — Checkout Logic

public class ExitGate extends Gate {
    public ExitGate(String gateId) { super(gateId); }

    public Payment processExit(Ticket ticket) {
        long duration = Duration.between(ticket.getEntryTime(), LocalDateTime.now()).toMinutes();
        double ratePerHour = 20.0;
        double amount = Math.ceil(duration / 60.0) * ratePerHour;

        ticket.getSlot().releaseVehicle();
        return new Payment(amount);
    }
}
Enter fullscreen mode Exit fullscreen mode

đź§± Step 5: Design Principles Applied

Principle Applied Where & How
S — SRP Ticket, ParkingSlot, Gate all have single clear responsibilities
O — OCP Vehicle types, payment strategies can be extended via enums or strategy pattern
L — LSP Entry/ExitGate are substitutable for Gate
I — ISP Interfaces like PaymentProcessor can be introduced for online/card/cash payments
D — DIP Payment service can be injected via abstraction

đź§° Optional Extensibility Add-ons (Post-MVP)

Feature Design Approach
Online Payment Strategy Pattern for PaymentProcessor
Admin Dashboard Observer Pattern on ParkingLot availability
Slot Allocation Algorithm Use Strategy pattern to support “closest slot”, “VIP slot”, etc.
Pricing Rules by Time Decorator/Strategy for PricingEngine

âś… Final Thoughts

This walkthrough demonstrates how to incrementally evolve a complex system while adhering to:

  • Domain modeling best practices
  • SOLID and OOP design
  • Extensibility-first mindset
  • Clean code boundaries

👉 A well-executed LLD interview is less about syntax and more about narrative clarity, logical separation of concerns, and engineering foresight.


Top comments (0)