← Home
🏗Crack System Design with DSA AnimatorSystem DesignNEW
🏗 System Design›LLD Interview Prep›LLD
🅿️

Design Parking Lot

The #1 LLD warm-up at Amazon, Microsoft, Flipkart, and most service companies — OOP, enums, spot allocation, and pricing in one clean design.

📊 Easy → Medium⏱ 35–45 min🏢 Amazon · Microsoft · Goldman · Flipkart · Oracle · most product SDE-2 loops
🎯 Interview goal

Model vehicles, spots (size-matched), floors, and a ParkingLot facade. Use Strategy for fees, Factory for vehicles, and keep APIs idempotent for entry/exit. Interviewers want clarifying questions first, then working code — not UML perfection.

📋

The interview question

“Design a parking lot system.” You are usually asked to support multiple vehicle types, multiple floors, park / unpark, and fee on exit.

At product companies the bar is: ask scope, draw 4–6 classes, then code the happy path in Java (or language of choice) with room to extend.

💡
Treat this as a facade + domain model problem, not a distributed system. No Kafka unless the interviewer explicitly scales the prompt.
❓

Clarifying questions (ask before coding)

Spend 3–5 minutes here. Interviewers score communication; silence loses points.

1What vehicle types? (Car, bike, truck?)
Why askDrives spot sizes and whether one spot fits only one size class.
2Single entrance/exit or multiple gates?
Why askAffects whether you need per-gate queues or one central controller.
3Pricing model — hourly, flat, different per vehicle type?
Why askNatural fit for Strategy pattern; avoid hard-coded if chains.
4Do we need display boards (“Floor 2: 14 free”)?
Why askOptional Observer or polling API — mention if time allows.
✅

Requirements & assumptions (state aloud)

  • Functional: park vehicle → issue ticket; unpark with ticket → compute fee; find nearest free compatible spot.
  • Vehicle types: BIKE, CAR, TRUCK mapped to spot sizes SMALL, MEDIUM, LARGE.
  • Structure: one ParkingLot with N Floors; each floor has a list of ParkingSpots.
  • Non-functional (v1): single process, in-memory, thread-safe spot booking optional.
  • Out of scope (v1): payment gateway, ANPR cameras, reservations, EV charging — list as extensions.
ScopeIn scope (v1)Extension (mention)
AllocationFirst free spot matching sizeNearest entrance, handicap spots
PricingHourly rate per vehicle typeWeekend surge, membership
ScaleOne buildingMulti-lot central billing
🧩

High-level design & class relationships

Keep ~6–8 classes visible on the whiteboard. ParkingLot is the facade interviewers interact with.

Spot allocation: scan floors in order; first ParkingSpot where spot.canFit(vehicle) and !spot.isOccupied().

  1. Enums: VehicleType, SpotSize, SpotStatus
  2. Domain: Vehicle, ParkingSpot, Floor, Ticket
  3. Services: ParkingLot (park/unpark), PricingStrategy
  4. Factory: VehicleFactory (optional but liked)
🚗 S🚗 S🚙 M🏍 B
🚛 L🚗 S🚗 S🚙 M

🟢 free 🔴 occupied — S/M/L/B = spot size for Car / medium / Truck / Bike

Parking lot component flow
Entry → lot → floors → exit with ticket & pricing
📐

UML class diagram (optional — if interviewer asks)

Not every interview requires a class diagram. Many interviewers want code first; others (especially Microsoft, Oracle, some Amazon loops) say *“draw the classes before you code.”*

When to draw: after clarifying requirements, before or in parallel with coding — takes 3–5 minutes. Say: *“I’ll sketch the main classes and relationships, then implement the facade.”*

What to include: 6–8 classes max, key fields, 2–3 methods per class, and relationships (composition > association > dependency). Skip getters/setters on the board.

💡
If they do not ask for UML, skip it and go straight to code — but keep the mental model below ready in case they say “can you draw it?” mid-way.
UML class diagram for parking lot
Figure — Main classes (draw relationships verbally or on the board if asked)

Relationships to label aloud

  • Composition (◆): ParkingLot → Floor → ParkingSpot — spots do not exist without the lot; lifecycle tied together.
  • Association: Ticket → Vehicle + ParkingSpot — ticket references what was parked and where.
  • Dependency (- -▷): ParkingLot uses PricingStrategy; VehicleFactory creates Vehicle.
  • Implementation (△): HourlyPricingStrategy implements PricingStrategy.

If interviewer says “simplify”

  • Drop VehicleFactory and construct Vehicle directly.
  • Merge enums into comments instead of separate enum boxes.
  • Keep only: ParkingLot, Floor, ParkingSpot, Vehicle, Ticket, PricingStrategy.
🎨

Design patterns used (and why)

⚠️
Do not force every GoF pattern. Mention Observer only if asked for live availability boards.
PatternWhereWhy (interview soundbite)
🏭Factory MethodVehicleFactory.create(type, plate)Centralizes vehicle creation; adding EV_CAR does not touch ParkingLot.
🎯StrategyPricingStrategy → HourlyPricingStrategySwap hourly / flat / dynamic pricing without changing exit flow.
🔒Singleton (optional)ParkingLot.getInstance()One physical lot per JVM — mention trade-off: hurts testing unless you allow injection.
🪞FacadeParkingLot public APIHides floors/spots/tickets — “park this car” is one call for clients/gates.
🎬State (light)ParkingSpot FREE ↔ OCCUPIEDIllegal transitions (park twice) are rejected inside the spot.
💻

Full solution — enums & domain model

1. Enums & core typesjava
public enum VehicleType { BIKE, CAR, TRUCK }

public enum SpotSize { SMALL, MEDIUM, LARGE }

public enum SpotStatus { FREE, OCCUPIED }

public final class VehicleSizeMapper {
    // Private ctor — utility class, no instances
    private VehicleSizeMapper() {}

    // Returns smallest spot size that can fit this vehicle type
    public static SpotSize requiredSpot(VehicleType type) {
        return switch (type) {
            case BIKE -> SpotSize.SMALL;
            case CAR  -> SpotSize.MEDIUM;
            case TRUCK -> SpotSize.LARGE;
        };
    }
}

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

    // Creates an immutable vehicle with plate and type
    public Vehicle(String licensePlate, VehicleType type) {
        this.licensePlate = Objects.requireNonNull(licensePlate);
        this.type = Objects.requireNonNull(type);
    }

    // Returns license plate for ticket / duplicate-entry checks
    public String getLicensePlate() { return licensePlate; }

    // Returns vehicle type for spot-size and pricing lookups
    public VehicleType getType() { return type; }
}
2. ParkingSpot & Floorjava
public class ParkingSpot {
    private final String id;
    private final SpotSize size;
    private SpotStatus status = SpotStatus.FREE;
    private Vehicle parkedVehicle;

    // Creates a spot with fixed id and size tier
    public ParkingSpot(String id, SpotSize size) {
        this.id = id;
        this.size = size;
    }

    // True if this spot is large enough for the vehicle type
    public boolean canFit(Vehicle vehicle) {
        SpotSize need = VehicleSizeMapper.requiredSpot(vehicle.getType());
        return size.ordinal() >= need.ordinal();
    }

    // Atomically parks vehicle; false if occupied or size mismatch
    public synchronized boolean park(Vehicle vehicle) {
        if (status == SpotStatus.OCCUPIED || !canFit(vehicle)) return false;
        this.parkedVehicle = vehicle;
        this.status = SpotStatus.OCCUPIED;
        return true;
    }

    // Frees spot and returns parked vehicle; empty if already free
    public synchronized Optional<Vehicle> unpark() {
        if (status == SpotStatus.FREE) return Optional.empty();
        Vehicle v = parkedVehicle;
        parkedVehicle = null;
        status = SpotStatus.FREE;
        return Optional.of(v);
    }

    // True when spot can accept a new park request
    public boolean isFree() { return status == SpotStatus.FREE; }

    // Returns spot id for tickets and display boards
    public String getId() { return id; }
}

public class Floor {
    private final int level;
    private final List<ParkingSpot> spots = new ArrayList<>();

    // Creates a floor at the given level number
    public Floor(int level) { this.level = level; }

    // Registers a spot on this floor
    public void addSpot(ParkingSpot spot) { spots.add(spot); }

    // Returns first free spot on this floor that fits the vehicle
    public Optional<ParkingSpot> findAvailableSpot(Vehicle vehicle) {
        return spots.stream()
            .filter(ParkingSpot::isFree)
            .filter(s -> s.canFit(vehicle))
            .findFirst();
    }

    // Counts free spots on this floor (for availability board)
    public long freeCount() {
        return spots.stream().filter(ParkingSpot::isFree).count();
    }

    // Read-only view of all spots on the floor
    public List<ParkingSpot> getSpots() { return Collections.unmodifiableList(spots); }
}
⚙️

Full solution — ticket, pricing & ParkingLot

3. Ticket & Strategy pricingjava
public class Ticket {
    private final String ticketId;
    private final Vehicle vehicle;
    private final ParkingSpot spot;
    private final Instant entryTime;

    // Issued on park: binds vehicle, spot, and entry time for fee on exit
    public Ticket(Vehicle vehicle, ParkingSpot spot) {
        this.ticketId = UUID.randomUUID().toString();
        this.vehicle = vehicle;
        this.spot = spot;
        this.entryTime = Instant.now();
    }

    // Id shown to user at gate for unpark
    public String getTicketId() { return ticketId; }

    // Used by pricing strategy to compute duration
    public Instant getEntryTime() { return entryTime; }

    // Spot to free when vehicle leaves
    public ParkingSpot getSpot() { return spot; }

    // Vehicle type drives hourly rate lookup
    public Vehicle getVehicle() { return vehicle; }
}

public interface PricingStrategy {
    // Computes exit fee from ticket and exit timestamp
    BigDecimal calculateFee(Ticket ticket, Instant exitTime);
}

public class HourlyPricingStrategy implements PricingStrategy {
    private final Map<VehicleType, BigDecimal> hourlyRate;

    // Loads per-vehicle-type hourly rates
    public HourlyPricingStrategy(Map<VehicleType, BigDecimal> hourlyRate) {
        this.hourlyRate = hourlyRate;
    }

    // Bills at least one hour; rate chosen by vehicle type on ticket
    @Override
    public BigDecimal calculateFee(Ticket ticket, Instant exitTime) {
        long minutes = Duration.between(ticket.getEntryTime(), exitTime).toMinutes();
        long hours = Math.max(1, (minutes + 59) / 60);
        BigDecimal rate = hourlyRate.get(ticket.getVehicle().getType());
        return rate.multiply(BigDecimal.valueOf(hours));
    }
}
4. ParkingLot facade (park / unpark)java
public class ParkingLot {
    private final String name;
    private final List<Floor> floors = new ArrayList<>();
    private final PricingStrategy pricingStrategy;
    private final Map<String, Ticket> activeTickets = new ConcurrentHashMap<>();

    // Builds lot with name and injectable pricing strategy
    public ParkingLot(String name, PricingStrategy pricingStrategy) {
        this.name = name;
        this.pricingStrategy = pricingStrategy;
    }

    // Adds a floor to the lot structure
    public void addFloor(Floor floor) { floors.add(floor); }

    // Finds compatible spot, parks vehicle, returns ticket; empty if full
    public synchronized Optional<Ticket> parkVehicle(Vehicle vehicle) {
        for (Floor floor : floors) {
            Optional<ParkingSpot> spot = floor.findAvailableSpot(vehicle);
            if (spot.isPresent() && spot.get().park(vehicle)) {
                Ticket ticket = new Ticket(vehicle, spot.get());
                activeTickets.put(ticket.getTicketId(), ticket);
                return Optional.of(ticket);
            }
        }
        return Optional.empty();
    }

    // Frees spot, removes ticket, returns fee; empty if invalid ticket id
    public synchronized Optional<BigDecimal> unparkVehicle(String ticketId) {
        Ticket ticket = activeTickets.remove(ticketId);
        if (ticket == null) return Optional.empty();

        ticket.getSpot().unpark();
        Instant exit = Instant.now();
        return Optional.of(pricingStrategy.calculateFee(ticket, exit));
    }

    // Counts free spots that can fit the given vehicle type (display board)
    public int availableSpots(VehicleType type) {
        Vehicle probe = new Vehicle("PROBE", type);
        return (int) floors.stream()
            .flatMap(f -> f.getSpots().stream())
            .filter(ParkingSpot::isFree)
            .filter(s -> s.canFit(probe))
            .count();
    }
}
5. Factory + demo mainjava
public class VehicleFactory {
    // Central place to construct vehicles (extend for new types later)
    public static Vehicle create(VehicleType type, String plate) {
        return new Vehicle(plate, type);
    }
}

public class ParkingLotDemo {
    // Wires floors, spots, park, and unpark with fee printout
    public static void main(String[] args) {
        Map<VehicleType, BigDecimal> rates = Map.of(
            VehicleType.BIKE, new BigDecimal("20"),
            VehicleType.CAR,  new BigDecimal("40"),
            VehicleType.TRUCK, new BigDecimal("80")
        );
        ParkingLot lot = new ParkingLot("City Mall", new HourlyPricingStrategy(rates));

        Floor f1 = new Floor(1);
        f1.addSpot(new ParkingSpot("F1-S1", SpotSize.SMALL));
        f1.addSpot(new ParkingSpot("F1-M1", SpotSize.MEDIUM));
        f1.addSpot(new ParkingSpot("F1-L1", SpotSize.LARGE));
        lot.addFloor(f1);

        Vehicle car = VehicleFactory.create(VehicleType.CAR, "KA-01-AB-1234");
        Optional<Ticket> ticket = lot.parkVehicle(car);
        ticket.ifPresent(t -> {
            System.out.println("Parked. Ticket: " + t.getTicketId());
            lot.unparkVehicle(t.getTicketId())
               .ifPresent(fee -> System.out.println("Fee: ₹" + fee));
        });
    }
}
🎤

While you code — questions interviewers ask

Expect these mid-coding — answer briefly, then keep typing.

🚀

Extensions & follow-ups (senior loop)

  • Display board: AvailabilityPublisher after each park/unpark (Observer).
  • Multiple entrances: Gate id on ticket; still one ParkingLot brain.
  • Electric vehicles: EVSpot subclass or decorator with charging fee.
  • Handicap / compact: enum flags on ParkingSpot.
  • Distributed lot: shard by floorId, Redis lock on spot id — only if interviewer converts to HLD.
⚠️

Common mistakes (fail signals)

  • Jumping to code without clarifying vehicle types and pricing.
  • God class ParkingLot with 400 lines and no Floor / Spot separation.
  • Hard-coded if (type == CAR) fee = 40 instead of Strategy.
  • Allowing any vehicle into any spot regardless of size.
  • No ticket id — unpark by license plate only (ambiguous for two entries).
  • Forgetting empty lot and invalid ticket paths.
🧾

60-second cheat sheet

Ask firstVehicles, pricing, gates, concurrency, persistence
Core classesVehicle, ParkingSpot, Floor, Ticket, ParkingLot
AllocateFirst free spot where size ≥ vehicle need
PatternsStrategy (fee), Factory (vehicle), Facade (lot)
Class diagramOptional — 6–8 classes; ◆ composition, △ implements
APIpark(Vehicle) → Ticket; unpark(ticketId) → fee
ExtendObserver board, reserved spots, repository
🔗

More LLD problems