Design a Parking Lot (LLD)

Difficulty: Advanced

Question

Design the class structure for a parking lot system. Identify the entities, relationships, and show how you would keep it extensible.

Answer

Low level design warm-ups like this test whether you can turn a vague requirement into a clean set of classes. There is no single right answer. What interviewers grade is your process: clarify, identify entities, assign responsibilities, and discuss extensibility. Here is the approach I would walk through.

Step one, clarify requirements aloud. Ask: how many levels? Which vehicle types (bike, car, truck)? Are spots typed by size? Is it paid, with hourly rates? Are there multiple entry and exit gates? Do we need payments, and is concurrency a concern? State your assumptions for the scope, for example: one lot, several spots of different types, park and unpark, tickets, and pricing on exit.

Step two, find the nouns; they become classes. Vehicle with a licence plate and a VehicleType enum (BIKE, CAR, TRUCK). ParkingSpot with an id, a type it supports, and the vehicle currently parked. Ticket linking a vehicle to a spot, with the entry time. ParkingLot as the facade owning spots and active tickets. Optionally Level or Floor, Gate, and Payment.

Step three, find the verbs; they become methods with clear owners. ParkingSpot can canFit(vehicle), park(vehicle), and free(). ParkingLot exposes park(vehicle), returning a Ticket, and unpark(plate), which frees the spot. Pricing belongs neither in Vehicle nor in ParkingSpot, but in a separate PricingStrategy so hourly, flat, or weekend rates can change independently. This is where you demonstrate SOLID: Single Responsibility keeps each class focused, and the Strategy pattern keeps the lot open to new pricing rules.

Step four, relationships. ParkingLot has many ParkingSpots and many active Tickets (composition, since spots do not exist without the lot). A Ticket refers to a Vehicle and a ParkingSpot (association). Vehicle is a value-like entity. If vehicle types need different behaviour, make Vehicle abstract with Car, Bike, and Truck subclasses, but if it is only a type tag, an enum is simpler. Say which you chose and why. Here I use an enum, because vehicles carry no distinct behaviour.

Step five, the allocation policy. The simple version scans for the first free compatible spot. Mention that the search strategy can be pluggable (nearest to entrance, lowest floor) and that you can keep a queue or set of free spots per type for O(1) allocation instead of a linear scan.

Step six, edge cases and extensions: a full lot (throw or return a clear error), an unknown ticket on exit, duplicate entry of the same plate, concurrency when two gates allocate simultaneously (synchronise on allocation or use a concurrent structure), payment integration, EV charging spots, and reservations.

The code below is a compact working core, with a pricing strategy shown separately. Notice how park and unpark leave the spot's state consistent, and how a full lot becomes a meaningful exception instead of a null.

Finish by drawing a small class diagram and offering to extend: multi-level support means adding a Level class holding spots, with the lot delegating to levels.

Code examples

Core entities and park/unpark flow

import java.util.*;

enum VehicleType { BIKE, CAR, TRUCK }

class Vehicle {
    final String plate;
    final VehicleType type;
    Vehicle(String plate, VehicleType type) { this.plate = plate; this.type = type; }
}

class ParkingSpot {
    final int id;
    final VehicleType type;
    private Vehicle parked;
    ParkingSpot(int id, VehicleType type) { this.id = id; this.type = type; }
    boolean canFit(Vehicle v) { return parked == null && v.type == type; }
    void park(Vehicle v) { parked = v; }
    void free() { parked = null; }
}

class Ticket {
    final String plate;
    final ParkingSpot spot;
    Ticket(String plate, ParkingSpot spot) { this.plate = plate; this.spot = spot; }
}

class ParkingLot {
    private final List<ParkingSpot> spots = new ArrayList<>();
    private final Map<String, Ticket> active = new HashMap<>();

    void addSpot(ParkingSpot s) { spots.add(s); }

    Ticket park(Vehicle v) {
        for (ParkingSpot s : spots) {
            if (s.canFit(v)) {
                s.park(v);
                Ticket t = new Ticket(v.plate, s);
                active.put(v.plate, t);
                return t;
            }
        }
        throw new IllegalStateException("Lot full for " + v.type);
    }

    void unpark(String plate) {
        Ticket t = active.remove(plate);
        if (t == null) throw new IllegalArgumentException("No ticket for " + plate);
        t.spot.free();
    }
}

public class ParkingLotDemo {
    public static void main(String[] args) {
        ParkingLot lot = new ParkingLot();
        lot.addSpot(new ParkingSpot(1, VehicleType.CAR));
        lot.addSpot(new ParkingSpot(2, VehicleType.BIKE));

        Ticket t = lot.park(new Vehicle("KA01AB1234", VehicleType.CAR));
        System.out.println("Parked at spot " + t.spot.id);

        try {
            lot.park(new Vehicle("MH12XY9999", VehicleType.CAR));
        } catch (IllegalStateException e) {
            System.out.println(e.getMessage());
        }

        lot.unpark("KA01AB1234");
        Ticket t2 = lot.park(new Vehicle("MH12XY9999", VehicleType.CAR));
        System.out.println("Parked at spot " + t2.spot.id);
    }
}

Pluggable pricing strategy

interface PricingStrategy { double fee(long minutes); }

class HourlyPricing implements PricingStrategy {
    private final double ratePerHour;
    HourlyPricing(double ratePerHour) { this.ratePerHour = ratePerHour; }
    public double fee(long minutes) {
        return Math.ceil(minutes / 60.0) * ratePerHour;
    }
}

public class PricingDemo {
    public static void main(String[] args) {
        PricingStrategy p = new HourlyPricing(30);
        System.out.println(p.fee(130));   // 130 minutes rounds up to 3 hours
    }
}

Key points

Concepts covered

LLD, class-design, parking-lot, strategy, entities