Skip to content

Design a parking lot

SDE II30 min readNeeds: Object modeling, Strategy

Ask before you draw. For this version we agree on:

  • Three spot sizes (small, medium, large) and three vehicle types (motorcycle, car, truck).
  • A vehicle can park in any spot at least as big as it needs.
  • Parking returns a ticket; leaving with that ticket frees the spot.
  • Two vehicles arriving at the same moment must never get the same spot.
  • Out of scope for now: payments, multiple floors, reservations.

Vehicle, ParkingSpot, Ticket and ParkingLot fall straight out of the requirements. The one that doesn’t is SpotAssignmentStrategy. Interviewers almost always follow up with “what if we want to fill the nearest spot first instead?”, and pulling that decision into its own type means the answer is a new class, not an edit to ParkingLot.

Parking lot: class diagram
classDiagram
  class ParkingLot {
    -List~ParkingSpot~ spots
    -Map~String,Ticket~ active
    +park(Vehicle) Optional~Ticket~
    +unpark(ticketId)
  }
  class SpotAssignmentStrategy {
    <<interface>>
    +pick(spots, vehicle) Optional~ParkingSpot~
  }
  class BestFitStrategy
  class ParkingSpot {
    +id
    +size: SpotSize
    +fits(Vehicle) bool
  }
  class Vehicle {
    +plate
    +type: VehicleType
  }
  class Ticket {
    +id
    +entry: Instant
  }
  ParkingLot o-- ParkingSpot
  ParkingLot --> SpotAssignmentStrategy
  SpotAssignmentStrategy <|.. BestFitStrategy
  ParkingLot --> Ticket : issues
  Ticket --> Vehicle
  Ticket --> ParkingSpot
Scroll or pinch to zoom, drag to pan
ParkingLot.java
import java.time.Instant;
import java.util.*;
enum SpotSize { SMALL, MEDIUM, LARGE }
enum VehicleType {
MOTORCYCLE(SpotSize.SMALL), CAR(SpotSize.MEDIUM), TRUCK(SpotSize.LARGE);
final SpotSize minSpot;
VehicleType(SpotSize minSpot) { this.minSpot = minSpot; }
}
record Vehicle(String plate, VehicleType type) {}
final class ParkingSpot {
final String id;
final SpotSize size;
private Vehicle parked;
ParkingSpot(String id, SpotSize size) { this.id = id; this.size = size; }
boolean fits(Vehicle v) { return parked == null && size.compareTo(v.type().minSpot) >= 0; }
void park(Vehicle v) { parked = v; }
void free() { parked = null; }
}
record Ticket(String id, Vehicle vehicle, ParkingSpot spot, Instant entry) {}
/** Decides which free spot a vehicle gets. Swappable without touching ParkingLot. */
interface SpotAssignmentStrategy {
Optional<ParkingSpot> pick(List<ParkingSpot> spots, Vehicle v);
}
/** Smallest spot that fits, so large spots stay free for trucks. */
final class BestFitStrategy implements SpotAssignmentStrategy {
public Optional<ParkingSpot> pick(List<ParkingSpot> spots, Vehicle v) {
return spots.stream()
.filter(s -> s.fits(v))
.min(Comparator.comparing(s -> s.size));
}
}
public final class ParkingLot {
private final List<ParkingSpot> spots;
private final SpotAssignmentStrategy strategy;
private final Map<String, Ticket> active = new HashMap<>();
public ParkingLot(List<ParkingSpot> spots, SpotAssignmentStrategy strategy) {
this.spots = List.copyOf(spots);
this.strategy = strategy;
}
// synchronized: two cars must never get the same spot. See "Alternatives" for finer locking.
public synchronized Optional<Ticket> park(Vehicle v) {
return strategy.pick(spots, v).map(spot -> {
spot.park(v);
Ticket t = new Ticket(UUID.randomUUID().toString(), v, spot, Instant.now());
active.put(t.id(), t);
return t;
});
}
public synchronized void unpark(String ticketId) {
Ticket t = active.remove(ticketId);
if (t == null) throw new IllegalArgumentException("Unknown ticket " + ticketId);
t.spot().free();
}
public static void main(String[] args) {
ParkingLot lot = new ParkingLot(
List.of(new ParkingSpot("S1", SpotSize.SMALL), new ParkingSpot("M1", SpotSize.MEDIUM),
new ParkingSpot("L1", SpotSize.LARGE)),
new BestFitStrategy());
Ticket bike = lot.park(new Vehicle("B-1", VehicleType.MOTORCYCLE)).orElseThrow();
Ticket car = lot.park(new Vehicle("C-1", VehicleType.CAR)).orElseThrow();
System.out.println(bike.spot().id + " " + car.spot().id); // S1 M1
lot.unpark(car.id());
}
}

The common upgrade is one free-spot queue per size, each with its own lock, so a motorcycle and a truck never contend. That trades simplicity for throughput, and it makes “best fit” harder because you now check several queues in order.

  • Multiple floors: a Level that owns spots, with the strategy choosing a level first.
  • Pricing: a PricingPolicy computed from the ticket’s entry time when the vehicle leaves.
  • Display boards showing free spots per size, updated by events instead of polling.