A full machine-coding walkthrough, part 1: scoping a parking lot system and translating it into a first-pass class diagram using the nouns-and-verbs technique.
Published September 23, 2026
"Design a parking lot" is one of the most common LLD prompts precisely because it's deceptively simple — it rewards candidates who scope it carefully and punishes anyone who starts coding immediately.
Asking these out loud (see Object-Oriented Design Refresher for why this matters generally) turns a vague prompt into a concrete one:
Stating these assumptions explicitly (rather than silently picking one and hoping it matches the interviewer's expectation) is itself part of what's being evaluated.
Applying the technique from the OOP Refresher directly to this prompt:
Nouns (candidate classes): ParkingLot, Floor, ParkingSpot, Vehicle, Ticket, Gate (Entry/Exit). Verbs (candidate methods): parkVehicle, unparkVehicle, findAvailableSpot, issueTicket, calculateFee, isFull.
ParkingLot
└── has many → Floor
└── has many → ParkingSpot
Vehicle ←→ Ticket (a ticket references the vehicle and the spot it occupies)
enum VehicleType { MOTORCYCLE, CAR, TRUCK }
enum SpotType { COMPACT, LARGE, HANDICAP }
abstract class Vehicle {
private final String licensePlate;
private final VehicleType type;
Vehicle(String licensePlate, VehicleType type) {
this.licensePlate = licensePlate; this.type = type;
}
VehicleType getType() { return type; }
}
class Car extends Vehicle { Car(String plate) { super(plate, VehicleType.CAR); } }
class Motorcycle extends Vehicle { Motorcycle(String plate) { super(plate, VehicleType.MOTORCYCLE); } }
class Truck extends Vehicle { Truck(String plate) { super(plate, VehicleType.TRUCK); } }
class ParkingSpot {
private final String id;
private final SpotType type;
private boolean occupied = false;
private Vehicle parkedVehicle;
ParkingSpot(String id, SpotType type) { this.id = id; this.type = type; }
boolean canFit(Vehicle vehicle) {
// a compact spot can't fit a truck; a large spot can fit anything smaller too
return switch (vehicle.getType()) {
case MOTORCYCLE -> true; // fits any spot type
case CAR -> type == SpotType.COMPACT || type == SpotType.LARGE;
case TRUCK -> type == SpotType.LARGE;
};
}
void park(Vehicle vehicle) { this.parkedVehicle = vehicle; this.occupied = true; }
void vacate() { this.parkedVehicle = null; this.occupied = false; }
boolean isOccupied() { return occupied; }
}
class Floor {
private final int floorNumber;
private final List<ParkingSpot> spots;
Floor(int floorNumber, List<ParkingSpot> spots) { this.floorNumber = floorNumber; this.spots = spots; }
Optional<ParkingSpot> findAvailableSpot(Vehicle vehicle) {
return spots.stream()
.filter(s -> !s.isOccupied() && s.canFit(vehicle))
.findFirst();
}
}
class Ticket {
private final String id;
private final Vehicle vehicle;
private final ParkingSpot spot;
private final Instant entryTime;
Ticket(String id, Vehicle vehicle, ParkingSpot spot) {
this.id = id; this.vehicle = vehicle; this.spot = spot; this.entryTime = Instant.now();
}
Instant getEntryTime() { return entryTime; }
ParkingSpot getSpot() { return spot; }
}
class ParkingLot {
private final List<Floor> floors;
ParkingLot(List<Floor> floors) { this.floors = floors; }
Optional<Ticket> parkVehicle(Vehicle vehicle) {
for (Floor floor : floors) {
Optional<ParkingSpot> spot = floor.findAvailableSpot(vehicle);
if (spot.isPresent()) {
spot.get().park(vehicle);
return Optional.of(new Ticket(UUID.randomUUID().toString(), vehicle, spot.get()));
}
}
return Optional.empty(); // lot is full for this vehicle type
}
}
ParkingLot has Floors, each has ParkingSpots — this is composition (see the OOP Refresher's composition-vs-inheritance section), not inheritance: a Floor isn't a kind of ParkingLot, it's a part of one. Drawing this relationship before writing a single line of code is what prevents a common beginner mistake in this exact prompt — modeling Floor extends ParkingLot or similar, which would be structurally wrong (a floor isn't a specialization of a parking lot, it's a component of one).
This lesson deliberately stops at structure — canFit() returning a boolean and parkVehicle() finding the first available spot are the simplest possible implementations. Parking Lot — Implementation covers making spot selection smarter (a Strategy-pattern-based allocation policy), fee calculation as its own swappable strategy, and the concurrency question every interviewer eventually asks: what happens when two vehicles request the same spot at the same time.
Q: Why is Vehicle abstract with subclasses, instead of just a VehicleType enum field on a single Vehicle class? A: Either works for this level of complexity — the abstract-class version scales better if different vehicle types eventually need genuinely different behavior (not just a different type label), while a single class with an enum field is simpler when the difference truly is just a classification. Stating this tradeoff explicitly is a stronger answer than picking one silently.
Q: Why does ParkingSpot.canFit() live on ParkingSpot rather than on Vehicle or ParkingLot? A: It's a judgment call, but placing it on ParkingSpot keeps the 'can this spot accept this vehicle' rule colocated with the spot's own type — an alternative that's equally defensible is a separate SpotAllocationStrategy (foreshadowing Part 2), which becomes preferable once allocation policy needs to vary independently of the spot's static type.
Q: What's missing from this design that a real system would need? A: Payment processing, a way to look up an active ticket by vehicle/spot at exit time (this design has no reverse-lookup structure yet), and handling a full lot gracefully at the gate rather than just returning an empty Optional — all reasonable follow-ups an interviewer might probe if time allows.