SevyDevy

Guide · System design

LLD: Design a Parking Lot (Classes, States, Walkthrough)

27 min readUpdated By Saket Bhatnagar

Researched and drafted with AI, reviewed and edited by Saket Bhatnagar.

In short

  • Parking lot low level design is the class-level plan for car park software: which classes exist, such as ParkingLot, Floor, ParkingSpot and Ticket, and which rules each one enforces.
  • Every spot has a type (bike, compact, large, or EV, a spot with an electric charger) and one state at a time: free, reserved, occupied or out of service.
  • A state machine, the fixed list of allowed moves between states, refuses every other change in code. Two strategies, small classes you can swap, pick the spot and work out the fee.
  • Two gates must never hand out the same spot. Make the check and the claim one atomic step, meaning no other gate can act on that spot in between, for example a synchronized method on the spot.
  • Beginners can stop after the section on two cars and one spot. Engineers get the late-timer bug that a synchronized check still allows, a full Java core tested on Java 17 and 21, and the database version.

What goes wrong when parking software has no clear classes or rules?

Spots get handed out twice, big spots fill up with small vehicles, and fees come out wrong. None of these needs a bigger server. Each one needs a clearer class, a clearer rule or a safer method.

Parking lot low level design is how you get those classes and rules right before the code is written. It is also a standard practice question for low level design interviews, because it is small but full of real rules.

Picture a common setup. A hospital car park has 4 floors, 640 spots, 2 entry gates and 2 exit gates, all working at once. The setup is made up, but each bug below is easy to write, and later sections reproduce four of them in code.

The car park's first software was one big class. Each spot had an isFree field, a stored true-or-false value. In its first month, the team logged five bugs.

  • Two cars entered through two gates in the same second, and both tickets named spot F2-C7.
  • The nearest-spot rule sent a bike to the large spot by the lift, so an ambulance van later found no large spot free.
  • Petrol cars filled the EV charging spots, and electric cars queued at the gate.
  • A car parked from 11 pm to 1 am got a wrong fee, because the code subtracted clock hours: 1 minus 23 is minus 22.
  • A spot with a broken sensor still showed as free on the display board, because isFree could only say yes or no.

In an interview, the trap is the opposite one: drawing servers, queues and databases when the interviewer wanted classes, states and rules for one car park. This guide builds those classes and fixes all five bugs.

What is parking lot low level design?

Parking lot low level design is the class-level plan for parking software: which objects exist, what each one knows, how they work together and which rules they enforce. It is about the code inside one program.

Low level design, or LLD, differs from high level design. High level design shows how big parts connect, such as servers, databases and queues. Low level design zooms into one part and shows its classes and methods.

The words you need first

Six words come up in every section. The table gives each one a plain meaning and an example from the car park.

Six words used throughout this guide
WordPlain meaningIn the car park
ClassA blueprint in codeParkingSpot
ObjectOne real thing built from a classSpot F2-C7, the seventh compact spot on floor 2
InterfaceA promise about which methods a class offers, without saying how they workPricingStrategy, which any rate card can fill in
EnumA type with a fixed list of valuesSpotType: BIKE, COMPACT, LARGE, EV
State machineA fixed set of states plus the moves allowed between themA spot going from free to reserved to occupied
ThreadOne line of work inside a program; several can run at the same timeEach gate runs on its own thread

What requirements should you clarify before designing a parking lot?

Clarify the vehicle types, spot types, gates, tickets, payment and what is out of scope. Five minutes of questions decides which classes you need and which ones you can skip.

For the hospital car park, the answers look like this.

  • Vehicles: bikes, cars, electric cars, and large vehicles such as vans and ambulances.
  • Spots: bike, compact, large and EV spots. EV means electric vehicle, and an EV spot has a charger.
  • Gates: 2 entry gates and 2 exit gates, all working at the same time.
  • Tickets: the entry gate prints a ticket that names a spot. The exit gate reads it and takes payment by card or UPI, India's instant phone payment system.
  • Display: a board on each floor shows how many spots of each type are free.
  • Lost ticket: the driver pays a flat Rs 300.

Leave online booking, monthly passes and number-plate cameras out for now. Say that you will show where they plug in at the end.

Then write down the fit rule: which vehicle may use which spot, smallest first. It drives the spot search later, so it deserves its own table.

The fit rule for the hospital car park (a design choice for this lot, not an industry standard)
VehicleSpots it may use, in orderNever offered
BikeBikeCompact, large, EV
Petrol or diesel carCompact, then largeEV
Electric carEV, then compactLarge
Large vehicleLargeBike, compact, EV

What classes do you need to design a parking lot?

You need about nine: ParkingLot, Floor, ParkingSpot, Vehicle, Ticket, EntryGate and ExitGate, plus two strategy interfaces, SpotAssigner and PricingStrategy. Each one owns one clear job and the data that job needs.

A simple trick finds most of them. Underline the nouns in the requirements: lot, floor, spot, vehicle, ticket, gate. Nouns usually become classes, and verbs such as park, pay and assign usually become methods.

Not every noun deserves its own class. Bike, Car and Van subclasses of Vehicle would all behave the same way. Only the type differs, so one VehicleType enum is enough.

The core classes and what each one owns
ClassWhat it knowsWhat it does
ParkingLotIts floors, the two strategies and the active ticketsRuns the entry and exit steps
FloorIts spots, grouped by typeCounts free spots for its display board
ParkingSpotIts id, type, state and the ticket holding itMoves between states, refusing bad moves
VehicleIts number plate and VehicleTypeHolds data only
TicketIts id, the vehicle, the spot and the entry timeProves when and where a car parked
EntryGateIts gate numberAsks for a spot and prints a ticket
ExitGateIts gate numberGets the fee, takes payment, frees the spot
SpotAssignerThe fit rule and the search orderFinds and reserves a fitting free spot
PricingStrategyOne rate cardTurns a parking time into a fee

Start the diagram at ParkingLot and follow the has-many arrows down to the spots. Then look at the gates: neither one searches spots or does fee maths itself.

Classes in the hospital car park

Notice that both gates stay thin. Spot search and pricing live in two swappable strategies.

Diagram connections:

  • ParkingLot to Floor (has many)
  • Floor to ParkingSpot (has many)
  • EntryGate to SpotAssigner (asks for a spot)
  • SpotAssigner to Floor (searches)
  • EntryGate to Ticket (creates)
  • ExitGate to Ticket (reads)
  • ExitGate to PricingStrategy (asks for the fee)
  • ParkingLot: Owns floors, strategies and active tickets
  • Floor: Holds its spots, grouped by type
  • ParkingSpot: One type, one state, one holder at a time
  • EntryGate: Asks for a spot and prints the ticket
  • SpotAssigner: Strategy that reserves a fitting free spot
  • Ticket: Vehicle, spot and entry time
  • ExitGate: Takes payment and frees the spot
  • PricingStrategy: Strategy that turns time into a fee

Keeping the gates thin follows the single responsibility idea: a class should have one reason to change. If the rate card changes, only the pricing class changes, and the gates never notice.

How do parking spot states work in a parking lot design?

Each spot is in exactly one state at a time, and it may only move along paths you allow. Free can become reserved, reserved can become occupied, and every move you did not list is refused.

Think of the ward desk in the hospital next door, which tracks every bed. Each bed has a kind, such as ICU or general, and a status: free, held for a patient on the way, occupied, or being cleaned.

The mapping is direct. Bed kind is spot type. A held bed is a reserved spot, an occupied bed is an occupied spot, and a bed being cleaned is a spot out of service: empty, but never offered.

Where the picture breaks: one clerk at one desk does one thing at a time. A car park has two entry gates acting at once, and that is exactly where the hardest bugs come from.

  • Free: nobody is using the spot, and it can be offered to the next car.
  • Reserved: a ticket names this spot, and the car is driving up the ramp to it.
  • Occupied: the floor sensor, a detector set into the spot, reports a vehicle there.
  • Out of service: staff blocked the spot, for example for a broken sensor or an oil spill.

Follow the arrows in the diagram. Notice the question box: a reserved spot goes back to free if no car arrives within 15 minutes, a timer value this design picked.

The life of one parking spot

Only these moves are legal. Notice there is no arrow from occupied to out of service, so staff cannot block a spot with a car in it.

Diagram connections:

  • Free to Reserved (Ticket printed)
  • Reserved to Car arrived in 15 minutes?
  • Car arrived in 15 minutes? to Occupied (Yes, sensor sees it)
  • Car arrived in 15 minutes? to Free (No, timer releases it)
  • Occupied to Free (Paid at the exit gate)
  • Free to Out of service (Staff block it)
  • Out of service to Free (Repaired)
  • Free: Counted as free on the floor board
  • Reserved: A ticket names this spot
  • Car arrived in 15 minutes?: Sensor reports, or the timer fires
  • Occupied: Floor sensor sees a vehicle
  • Out of service: Skipped by the spot search

Here is the same machine in Java. The official Java tutorial describes an enum as a special data type for a variable that must equal one of a set of predefined constants. Java enums can also carry methods.

java
enum SpotType { BIKE, COMPACT, LARGE, EV }

enum SpotState {
    FREE, RESERVED, OCCUPIED, OUT_OF_SERVICE;

    boolean canMoveTo(SpotState next) {
        return switch (this) {
            case FREE -> next == RESERVED || next == OUT_OF_SERVICE;
            case RESERVED -> next == OCCUPIED || next == FREE;
            case OCCUPIED -> next == FREE;
            case OUT_OF_SERVICE -> next == FREE;
        };
    }
}

The canMoveTo method is the whole state machine. Each case lists the only states a spot may move to next, and every other move returns false.

The switch expression, a switch that returns a value, became a standard part of Java in JDK 14. Java's compiler, javac, refuses one that misses a value. Delete the OUT_OF_SERVICE line and javac reports that the switch expression does not cover all possible input values.

You may hear about the State pattern here. Refactoring.Guru describes it as letting an object alter its behaviour when its internal state changes, usually with one class per state. For four states that differ only in their allowed moves, an enum is clearer.

How do you assign a parking spot to a vehicle?

A SpotAssigner tries the smallest spot type that fits the vehicle first, searches the floors in a fixed order, and reserves the first free spot it finds. The fit rule and the search order both live in this one class.

SpotAssigner is an interface. The version in this guide, SmallestFitFirst, tries every compact spot on every floor before it offers a car a large one. This is the Strategy pattern: each way of doing a job sits in its own class, so you can swap them.

Swapping matters. The hospital may later want families parked close to the lifts, and a mall might fill its top floor first. Each is a new SpotAssigner class, and the gates never change.

The rule matters more than it looks. We ran a naive rule, the nearest spot that is big enough, against SmallestFitFirst on one floor. Its spots sit in walking order from the lift: large, EV, bike, compact, compact.

Four arrivals on one floor under two spot rules (we ran both)
Arrival, in orderNearest big-enough spotSmallestFitFirst
BikeLarge spot by the liftBike spot
Petrol carEV spotCompact spot
Electric carCompact spot, no chargerEV spot
Ambulance vanTurned awayLarge spot

What just happened? The naive rule looked sensible at every step, yet it gave the bike the only large spot and the petrol car the only charger. The van was turned away while a compact spot stood free, because a van does not fit one.

How do you stop two cars from getting the same parking spot?

Make the check and the claim one atomic step, meaning no other gate can act on that spot in between. Then only one gate can move a spot from free to reserved. The other gate's claim fails, and it tries the next spot.

Each gate runs on its own thread, so both gates can work at the same moment. Here is how the team's first software, which checked and then wrote with no lock, went wrong at 9 am.

  1. Gate 1 checks spot F2-C7

    The thread for gate 1 reads the spot's state and sees FREE.

  2. Gate 2 checks the same spot

    Before gate 1 writes anything, the thread for gate 2 reads the same spot. It also sees FREE.

  3. Both gates write RESERVED

    Gate 1 marks the spot reserved and prints a ticket. Gate 2 does exactly the same a moment later.

  4. Two cars, one spot

    Two drivers go up the ramp to F2-C7. One of them circles floor 2 looking for a space, then complains at the exit.

This is a race condition: the result depends on which thread happens to run first. The fix is to make the read and the write impossible to split. In Java, the simplest tool is the synchronized keyword.

Here is a first version of ParkingSpot. Keep it in mind, because a later section finds a bug that it still allows.

java
class ParkingSpot {
    private final String id;
    private final SpotType type;
    private SpotState state = SpotState.FREE;

    ParkingSpot(String id, SpotType type) {
        this.id = id;
        this.type = type;
    }

    synchronized boolean moveTo(SpotState next) {
        if (!state.canMoveTo(next)) return false;
        state = next;
        return true;
    }
}

The Java tutorial gives synchronized methods two effects. Two synchronized calls on the same object cannot interleave, so gate 2 waits for gate 1. And when a synchronized method exits, its changes become visible to the next synchronized call on that object.

Whichever gate runs second sees RESERVED, so canMoveTo returns false and its claim fails. The lock belongs to one spot, so gate 1 holding F2-C7 never slows gate 2 working on F3-C12.

Races are rare, which is why testing by hand misses them. We started two threads claiming one spot together, 100,000 times per batch, in five batches in one run on a 2-core machine.

Without a lock, both gates won the same spot 0 to 2 times per batch, and up to 5 in an earlier run. With the synchronized moveTo, it never happened. Those counts depend on the machine; the point is that the unlocked bug is real and rare.

Which bug does a synchronized spot method still allow?

A late timer. The moveTo method checks only whether the next state is allowed from the current one. It never checks whether the caller still has the right to make that move.

Pause and predict. A car reserves F2-C7 and reaches it at minute 14, so the sensor moves the spot to occupied. A moment later the 15-minute timer fires and calls moveTo(FREE). What happens?

The move succeeds, because occupied to free is legal: the exit gate needs it. We ran this exact sequence on version 1. The spot showed as free with a car standing in it, so the next car could be sent there.

A sneakier version follows the same path. Car A's timer is delayed, A parks and leaves, and car B reserves the same spot. When A's timer finally fires, it releases B's reservation.

This is a form of what engineers call the ABA problem: the state looks the same as before, but it now belongs to someone else. A check on the state alone cannot tell the two apart.

The fix has two parts. Each caller asks for one exact move, such as expire, which only works from reserved. And the spot remembers which ticket holds it, so only that ticket's timer or exit can free it.

java
final class ParkingSpot {
    final String id;
    final SpotType type;
    private SpotState state = SpotState.FREE;
    private String holder; // id of the ticket using this spot, or null

    ParkingSpot(String id, SpotType type) {
        this.id = id;
        this.type = type;
    }

    synchronized boolean reserve(String ticketId) {      // entry gate
        if (!moveFrom(SpotState.FREE, SpotState.RESERVED)) return false;
        holder = ticketId;
        return true;
    }

    synchronized boolean occupy() {                       // floor sensor
        return moveFrom(SpotState.RESERVED, SpotState.OCCUPIED);
    }

    synchronized boolean expire(String ticketId) {        // 15-minute timer
        return ticketId.equals(holder)
                && moveFrom(SpotState.RESERVED, SpotState.FREE);
    }

    synchronized boolean release(String ticketId) {       // exit gate
        return ticketId.equals(holder)
                && (moveFrom(SpotState.OCCUPIED, SpotState.FREE)
                    || moveFrom(SpotState.RESERVED, SpotState.FREE));
    }

    synchronized boolean block() {                        // staff
        return moveFrom(SpotState.FREE, SpotState.OUT_OF_SERVICE);
    }

    synchronized boolean repair() {                       // staff
        return moveFrom(SpotState.OUT_OF_SERVICE, SpotState.FREE);
    }

    synchronized SpotState state() {
        return state;
    }

    private boolean moveFrom(SpotState from, SpotState to) {
        if (state != from || !from.canMoveTo(to)) return false;
        state = to;
        if (to == SpotState.FREE) holder = null;
        return true;
    }
}

Every method except the private moveFrom is synchronized, and every state change goes through moveFrom. It checks the starting state as well as canMoveTo. The holder field stores the ticket id, so a stale timer or a wrong ticket gets false back.

We ran both bug cases on this version. The late timer returned false and the spot stayed occupied. Car A's stale timer returned false and B's reservation stayed. A sensor reporting a car on a free spot also gets false.

How do you calculate the parking fee in a low level design?

Give pricing its own PricingStrategy interface, with one method that takes the vehicle type, the entry time and the exit time and returns the fee. Each rate card is a separate class behind it.

The hospital car park uses a slab rate: a fixed price for the first block of time, then a price for each extra block. A car pays Rs 40 for the first 2 hours, then Rs 20 for each started hour. A bike pays half, and every other vehicle pays the car rate.

java
interface PricingStrategy {
    long feeInPaise(VehicleType v, LocalDateTime entry, LocalDateTime exit);
}

final class HospitalSlabPricing implements PricingStrategy {
    public long feeInPaise(VehicleType v, LocalDateTime entry, LocalDateTime exit) {
        long minutes = Duration.between(entry, exit).toMinutes();
        long extraHours = Math.max(0, (minutes - 120 + 59) / 60); // started hours after 2
        long fee = 4_000 + extraHours * 2_000;                     // Rs 40, then Rs 20 an hour
        return v == VehicleType.BIKE ? fee / 2 : fee;
    }
}

The fee is in paise, a hundredth of a rupee, held in a long, Java's 64-bit whole number. Adding 59 before dividing rounds extra minutes up to whole hours. Math.max stops a short stay from counting negative extra hours, so it pays the Rs 40 minimum.

Here is a worked example. A car enters at 9:10 am and leaves at 1:40 pm, which is 270 minutes. The first 120 cost Rs 40. The other 150 minutes start three hours, so Rs 60 more, for Rs 100.

Now the midnight bug. Subtracting clock hours gives 1 minus 23, which is minus 22. Java's Duration.between, which measures the time between two date-times, returns PT2H for this stay. That is Java's way of printing 2 hours, so the fee is Rs 40.

Fees returned by HospitalSlabPricing (we ran every row)
StayTime parkedFee
Car, in and out at once0 minutesRs 40
Car, 9:00 to 11:002 hoursRs 40
Car, 9:00:00 to 11:00:592 hours 59 seconds, counted as 120 minutesRs 40
Car, 9:00 to 11:012 hours 1 minuteRs 60
Car, 9:10 am to 1:40 pm4 hours 30 minutesRs 100
Bike, 9:10 am to 1:40 pm4 hours 30 minutesRs 50
Car, 11 pm to 1 am next day2 hoursRs 40
Car, 9 am to 9 am next day24 hoursRs 480

One limit applies. Oracle's documentation says LocalDateTime does not store a time zone, so Duration.between counts clock time. Where clocks change for daylight saving, use ZonedDateTime, a date-time with its zone, or Instant, a point on the global time-line.

We checked the gap. From 00:30 to 02:30 on 1 November 2026 measured 2 hours as LocalDateTime, but 3 hours in the America/New_York zone, where clocks go back that night.

  • LostTicketPricing: a flat Rs 300 when the driver has no ticket.
  • A monthly pass: a strategy that returns zero for pass holders, with no change to the gates.

How do you run the parking lot code and check the output?

Paste the blocks into one file, compile it and compare the output below. You need a JDK, the Java Development Kit, at version 16 or newer, because the code uses records, a short way to declare classes that only carry data.

Create ParkingDemo.java and start it with these imports. Then paste the SpotState block, the second ParkingSpot block, the fee block and the three Java blocks below. Only the imports must come first; Java accepts classes in any order.

java
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;

Next comes the code the earlier sections described in words: Vehicle, Floor and the assigner with its fit rule. The floors are built before the gate threads start and never change afterwards. The Java language specification says starting a thread happens-before its actions, a guarantee that everything done before start() is visible to the new thread. So every gate sees the finished floors.

java
enum VehicleType { BIKE, CAR, ELECTRIC_CAR, LARGE }

record Vehicle(String plate, VehicleType type) {}

final class Floor {
    final int number;
    private final Map<SpotType, List<ParkingSpot>> spots = new EnumMap<>(SpotType.class);

    Floor(int number) {
        this.number = number;
    }

    void add(ParkingSpot spot) {
        spots.computeIfAbsent(spot.type, t -> new ArrayList<>()).add(spot);
    }

    List<ParkingSpot> spots(SpotType type) {
        return spots.getOrDefault(type, List.of());
    }

    long freeCount(SpotType type) {
        return spots(type).stream().filter(s -> s.state() == SpotState.FREE).count();
    }
}

interface SpotAssigner {
    Optional<ParkingSpot> reserveSpot(List<Floor> floors, VehicleType v, String ticketId);
}

final class SmallestFitFirst implements SpotAssigner {
    // The fit rule: spot types a vehicle may use, smallest first.
    static List<SpotType> fits(VehicleType v) {
        return switch (v) {
            case BIKE -> List.of(SpotType.BIKE);
            case CAR -> List.of(SpotType.COMPACT, SpotType.LARGE);
            case ELECTRIC_CAR -> List.of(SpotType.EV, SpotType.COMPACT);
            case LARGE -> List.of(SpotType.LARGE);
        };
    }

    public Optional<ParkingSpot> reserveSpot(List<Floor> floors, VehicleType v, String ticketId) {
        for (SpotType type : fits(v))           // smallest fitting type first
            for (Floor floor : floors)          // then the lowest floor
                for (ParkingSpot spot : floor.spots(type))
                    if (spot.reserve(ticketId)) return Optional.of(spot);
        return Optional.empty();                // full for this vehicle type
    }
}

The lot ties everything together, with ParkingLot's enter and exit methods doing the two gates' work. The exit works out the fee before it frees the spot.

This demo treats payment as instant. A real exit gate would quote the fee, take payment, and only then close the ticket and free the spot, so the driver can retry a declined card.

java
record Ticket(String id, Vehicle vehicle, ParkingSpot spot, LocalDateTime entry) {}

final class ParkingLot {
    private final List<Floor> floors;
    private final SpotAssigner assigner;
    private final PricingStrategy pricing;
    private final ConcurrentMap<String, Ticket> active = new ConcurrentHashMap<>();
    private final AtomicLong nextId = new AtomicLong(1);

    ParkingLot(List<Floor> floors, SpotAssigner assigner, PricingStrategy pricing) {
        this.floors = List.copyOf(floors);
        this.assigner = assigner;
        this.pricing = pricing;
    }

    Optional<Ticket> enter(Vehicle v, LocalDateTime now) {           // entry gate
        String id = "T" + nextId.getAndIncrement();
        Optional<Ticket> ticket = assigner.reserveSpot(floors, v.type(), id)
                .map(spot -> new Ticket(id, v, spot, now));
        ticket.ifPresent(t -> active.put(t.id(), t));
        return ticket;
    }

    OptionalLong exit(String ticketId, LocalDateTime now) {         // exit gate
        Ticket t = active.get(ticketId);
        if (t == null || !active.remove(ticketId, t)) return OptionalLong.empty();
        long fee = pricing.feeInPaise(t.vehicle().type(), t.entry(), now);
        t.spot().release(ticketId);                                 // payment assumed done
        return OptionalLong.of(fee);
    }

    boolean expireIfNotArrived(Ticket t) {                          // 15-minute timer
        return t.spot().expire(t.id()) && active.remove(t.id(), t);
    }
}

Active tickets live in a ConcurrentHashMap. The Java SE 21 documentation for ConcurrentMap, which it implements, says remove(key, value) is performed atomically, so two exit gates cannot both close one ticket. We ran two exits on one ticket 20,000 times: exactly one was charged every time.

Finally, the demo builds two floors. Each has one bike, two compact, one large and one EV spot, and ten vehicles arrive.

java
class ParkingDemo {
    static String rupees(long paise) {
        return String.format("Rs %d.%02d", paise / 100, paise % 100);
    }

    static String park(ParkingLot lot, String plate, VehicleType type, LocalDateTime at) {
        Optional<Ticket> t = lot.enter(new Vehicle(plate, type), at);
        System.out.println(plate + " " + type + " -> "
                + t.map(x -> x.id() + " at " + x.spot().id).orElse("lot full for this type"));
        t.ifPresent(x -> x.spot().occupy());     // the floor sensor sees the car
        return t.map(Ticket::id).orElse(null);
    }

    public static void main(String[] args) {
        List<Floor> floors = new ArrayList<>();
        for (int f = 1; f <= 2; f++) {
            Floor floor = new Floor(f);
            for (SpotType type : SpotType.values())
                for (int n = 1; n <= (type == SpotType.COMPACT ? 2 : 1); n++)
                    floor.add(new ParkingSpot("F" + f + "-" + type.name().charAt(0) + n, type));
            floors.add(floor);
        }
        ParkingLot lot = new ParkingLot(floors, new SmallestFitFirst(), new HospitalSlabPricing());
        LocalDateTime nine = LocalDateTime.of(2026, 10, 19, 9, 10);

        String first = park(lot, "KA01", VehicleType.CAR, nine);
        park(lot, "KA02", VehicleType.BIKE, nine);
        for (String plate : List.of("KA03", "KA04", "KA05", "KA06"))
            park(lot, plate, VehicleType.CAR, nine);
        park(lot, "KA07", VehicleType.LARGE, nine);
        park(lot, "KA08", VehicleType.ELECTRIC_CAR, nine);
        park(lot, "KA09", VehicleType.CAR, nine);
        System.out.println("Free EV spots on floor 2: " + floors.get(1).freeCount(SpotType.EV));

        LocalDateTime out = LocalDateTime.of(2026, 10, 19, 13, 40);
        System.out.println(first + " pays " + rupees(lot.exit(first, out).orElseThrow()));
        System.out.println(first + " again: " + (lot.exit(first, out).isPresent() ? "paid twice" : "already closed"));

        String night = park(lot, "KA10", VehicleType.CAR, LocalDateTime.of(2026, 10, 19, 23, 0));
        System.out.println(night + " pays " + rupees(lot.exit(night, LocalDateTime.of(2026, 10, 20, 1, 0)).orElseThrow()));
    }
}
bash
javac -Xlint:all ParkingDemo.java
java ParkingDemo

Expected output, identical on OpenJDK 17.0.20 and 21.0.12 as of 19 October 2026:

text
KA01 CAR -> T1 at F1-C1
KA02 BIKE -> T2 at F1-B1
KA03 CAR -> T3 at F1-C2
KA04 CAR -> T4 at F2-C1
KA05 CAR -> T5 at F2-C2
KA06 CAR -> T6 at F1-L1
KA07 LARGE -> T7 at F2-L1
KA08 ELECTRIC_CAR -> T8 at F1-E1
KA09 CAR -> lot full for this type
Free EV spots on floor 2: 1
T1 pays Rs 100.00
T1 again: already closed
KA10 CAR -> T10 at F1-C1
T10 pays Rs 40.00

What just happened? Four cars filled the four compact spots before KA06 got a large one. KA09 was turned away while F2-E1 stood free, because petrol cars never get EV spots. T9 is missing because the refused car used that number.

T1 paid Rs 100 for 9:10 am to 1:40 pm, and its second exit was refused. KA10 then took the freed spot F1-C1 and paid Rs 40 for a stay across midnight.

How does the claim work when gates run on several servers?

Move the check and the claim into the database as one conditional update. A Java lock lives inside one program, so it cannot stop a gate that runs on another server.

sql
UPDATE spot
SET state = 'RESERVED', ticket_id = 'T1'
WHERE id = 'F2-C7' AND state = 'FREE';

Then check how many rows changed. One row means this gate won the spot, and zero means another gate got there first, so try the next spot. In our test on SQLite, a small database that runs inside the program, the first claim changed 1 row and the second changed 0.

The PostgreSQL 18 documentation, current as of 19 October 2026, explains why this holds at its default isolation level, Read Committed. An isolation level sets what one transaction, a group of database changes that succeed or fail together, sees of another's changes.

A second updater waits until the first commits, saving its changes for good, or rolls back, undoing them. After a commit, it re-checks the WHERE clause on the new row. That row now says RESERVED, so nothing changes.

Store the ticket id in the same row for the same reason as the holder field. A timer's release should say WHERE state = 'RESERVED' AND ticket_id matches, so a stale timer changes zero rows.

With gates on several servers, ticket ids must also be unique across all of them, not counted per server as in the Java demo. Otherwise two servers can both issue T1, and one server's stale timer frees the other's reservation. We ran that case on SQLite, and the stale update changed 1 row. A database sequence, a counter the database hands out, or a random UUID, a 128-bit id, avoids it.

What edge cases break a parking lot design?

Most edge cases come from the physical world disagreeing with the software. Here are the ones worth naming, with what the code in this guide does.

  • A car parks in the wrong spot. The sensor reports a car on a free spot, occupy returns false, and the code should alert staff. A simple floor sensor sees a vehicle, not which vehicle. If the spot is reserved for another ticket instead, occupy succeeds, and the reserved driver arrives to find it taken.
  • A sensor breaks. Staff block the spot, it leaves the free count, and the search skips it. Blocking an occupied spot is refused, so a parked car is never hidden.
  • A driver loses the ticket. Staff look up the ticket by number plate, and LostTicketPricing charges the flat Rs 300.
  • The lot is full for one type. The assigner returns nothing, and the board shows full for that type while other types may still have room.
  • The exit clock is behind the entry clock. Duration.between goes negative and this pricing charges the Rs 40 minimum, so use one clock source for every gate.
  • The search feels slow. In five single-thread runs on Java 21, a search that checked all 640 spots took 13 to 30 microseconds, so a lot this size needs nothing cleverer.

How do you explain parking lot low level design in a system design interview?

Use a fixed order: requirements, classes, spot states, one car in, one car out, then concurrency and extensions. Talk while you draw, and tie each choice back to a requirement.

Here is the entry path to draw. Follow one car from the gate, and notice the question box: the lot can be full for this vehicle type while other spots are free.

One car entering the lot

Notice that the assigner reserves the spot in one atomic step before the ticket prints, so two gates never share one.

Diagram connections:

  • Car reaches entry gate to Find and reserve a spot
  • Find and reserve a spot to Got a spot?
  • Got a spot? to Print ticket for F2-C7 (Yes)
  • Got a spot? to Show full for this type (No)
  • Print ticket for F2-C7 to Mark spot occupied (Car parks)
  • Car reaches entry gate: Driver → EntryGate
  • Find and reserve a spot: EntryGate → SpotAssigner
  • Got a spot?: SpotAssigner → EntryGate
  • Show full for this type: EntryGate → Driver
  • Print ticket for F2-C7: EntryGate → Driver
  • Mark spot occupied: Floor sensor → ParkingSpot
  1. Clarify for five minutes

    Ask about vehicle types, spot types, gates, payment and lost tickets. Write the fit rule down, and agree on what is out of scope.

  2. Name the classes

    List the nouns and turn them into classes. Say why VehicleType is an enum and not a tree of subclasses.

  3. Draw the spot states

    Show free, reserved, occupied and out of service, with the allowed moves. Say that every other move is refused.

  4. Walk one car in

    Gate, assigner, reserve, ticket, sensor. Point out where smallest fit first keeps large spots for large vehicles.

  5. Walk the same car out

    Ticket, pricing strategy, payment, spot back to free. Work one fee out loud, including a stay across midnight.

  6. Handle two gates at once

    Name the race on one spot and show the synchronized claim. Add the ticket id check for late timers, and the conditional update for several servers.

  7. Code the core, then extend

    Write ParkingSpot and SpotState in real code. Finish by showing where online booking or monthly passes would plug in.

Extensions are where a good design shows. Online booking is another way to reach reserved, with a longer timer. A monthly pass is another PricingStrategy. Neither one needs a change to ParkingSpot.

Mistakes that cost the round

  • Drawing servers and databases in a low level round.
  • One ParkingLot class with forty methods that mixes search, pricing and payment.
  • A boolean isFree field, which cannot say reserved or out of service.
  • Fee maths inside the exit gate, so every new rate card means editing it.
  • No word on concurrency, even though the requirements said two entry gates.

Questions people ask

Is the parking lot question still asked in LLD interviews?
It remains a standard practice question for low level design rounds, though nobody publishes how often it is asked. It fits in one round, yet it tests requirements, class design, state rules, design patterns and concurrency. Expect a twist, such as EV spots, online booking or several gates, to see how your design bends.
Which design patterns are used in a parking lot design?
Strategy is the main one, used for spot assignment and for pricing, so each can be swapped without touching the gates. The State pattern is optional; an enum is clearer when states differ only in their allowed moves. Some candidates add a Factory, a class that creates objects for you, or a Singleton, a class allowed only one instance. Use a pattern only when it solves a problem you can name.
Should Vehicle be an abstract class or an enum?
Use a Vehicle class with a VehicleType enum field when all vehicles behave the same way and differ only in size. Use an abstract class with subclasses only when each kind has truly different behaviour. In most parking lot rounds the enum is simpler, and the fit rule table carries the differences.
How do you handle multiple entry gates in parking lot LLD?
Let each gate run on its own thread, and make claiming a spot one atomic step. A synchronized method on ParkingSpot that checks the state and changes it together is the simplest way. Store the ticket id on the spot so late timers cannot free it. With several servers, use a conditional update in a shared database.
What is the difference between low level and high level design of a parking lot?
High level design shows the big parts: gate devices, servers, a database, a payment service and how they talk over the network. Low level design zooms into the code: the classes, their fields and methods, the spot states and the rules. Many interviews ask for one, so check which one you are in.
How much code should I write in a parking lot interview?
Write real code for the core: usually ParkingSpot, the state enum and one strategy interface. Name the other classes and their main methods without full bodies. Interviewers care more about clear responsibilities and correct rules than about typing every getter, so spend the time on the state checks and the claim.

Sources and further reading

Practise this with Lala

Practise