Parking Lot
Design an object-oriented parking lot with multiple levels, multiple spot sizes (motorcycle, compact, large), and dynamic pricing. A ticket is issued on entry and used to compute the bill on exit.
Clarifying questions
- Single lot or multi-site? Are we modeling one site or a fleet?
- Is pricing hourly, tiered, or does it vary by time of day and vehicle size?
- Do we support reservations and prepaid spots, or only walk-up?
- How do payments work — cash at gate, card at kiosk, or app-based?
- Do we need to handle lost tickets and partial-day rounding?
Core requirements
- Park and unpark vehicles of varying sizes into compatible spots.
- Issue a ticket on entry that uniquely identifies the parking session.
- Compute fee on exit based on duration and vehicle/spot class.
- Track real-time availability per level and per spot size.
- Reject entry when no compatible spot exists.
- Support pluggable pricing strategies.
Canonical class breakdown
ParkingLot — Top-level aggregate that owns Levels, accepts park/unpark requests, and delegates spot assignment.
Level — Owns a collection of ParkingSpots and tracks availability counters per size for O(1) lookup.
ParkingSpot — A single physical spot with a size and current occupant; knows whether a given vehicle fits.
Vehicle (abstract) / Motorcycle, Car, Truck — Encodes size and license plate; subclasses exist mainly so spot-fit logic stays polymorphic.
Ticket — Immutable record of entry time, assigned spot, and vehicle reference; the unit of billing.
PricingStrategy (interface) — Computes fee for a Ticket at exit; concrete impls handle flat, tiered, surge, validated pricing.
SpotAssignmentStrategy — Chooses which compatible spot to allocate (nearest, lowest level, balanced) — kept separate from Level.
PaymentProcessor — Wraps the payment gateway; returns a receipt or throws so Ticket close-out is atomic.
Java sketchjava
// Java skeleton
enum VehicleSize { MOTORCYCLE, COMPACT, LARGE }
abstract class Vehicle {
protected String licensePlate;
public abstract VehicleSize getSize();
}
class Motorcycle extends Vehicle { public VehicleSize getSize() { return VehicleSize.MOTORCYCLE; } }
class Car extends Vehicle { public VehicleSize getSize() { return VehicleSize.COMPACT; } }
class Truck extends Vehicle { public VehicleSize getSize() { return VehicleSize.LARGE; } }
class ParkingSpot {
private final int spotId;
private final int level;
private final VehicleSize size;
private Vehicle vehicle;
public boolean canFit(Vehicle v) { /* spot.size >= v.size */ }
public boolean park(Vehicle v) { ... }
public Vehicle leave() { ... }
}
class Level {
private final int floor;
private final List<ParkingSpot> spots;
private final Map<VehicleSize, Integer> availableBySize; // O(1) counters
public ParkingSpot findSpot(Vehicle v, SpotAssignmentStrategy s) { ... }
public void release(ParkingSpot spot) { ... }
}
class Ticket {
private final String ticketId;
private final Instant entryTime;
private final ParkingSpot spot;
private final Vehicle vehicle;
}
class Receipt {
private final String ticketId;
private final BigDecimal amount;
private final Instant exitTime;
}
interface PricingStrategy {
BigDecimal computeFee(Ticket ticket, Instant exitTime);
}
interface SpotAssignmentStrategy {
ParkingSpot choose(List<ParkingSpot> candidates, Vehicle v);
}
interface PaymentProcessor {
Receipt charge(Ticket ticket, BigDecimal amount); // throws on failure
}
class ParkingLot {
private final List<Level> levels;
private final PricingStrategy pricing;
private final SpotAssignmentStrategy assignment;
private final PaymentProcessor payments;
public Ticket park(Vehicle v) { /* reject if no fit */ }
public Receipt unpark(Ticket t) { ... }
}
Key decisions to defend
- VehicleSize is an enum, but Vehicle is a class hierarchy — keeps fit logic polymorphic without exploding subclasses.
- Strategy pattern for pricing because rates change weekly and vary by site; don't bake into ParkingLot.
- Per-size availability counters on Level beat scanning the spot list; pay the bookkeeping cost on park/unpark.
- Ticket is immutable post-issue; close-out produces a separate Receipt rather than mutating the ticket.
- Spot assignment is its own strategy — 'nearest to entrance' and 'lowest level first' should not require a Level rewrite.
Likely follow-ups
- · How would you scale to 100 lots with a central availability dashboard?
- · Add reservations with a TTL (Time To Live) — where does the hold live?
- · Support EV charging spots — new size or a capability flag?
- · Two cars race for the last spot — how do you prevent double-allocation?
Dive deeper
This question tests whether you separate physical model (Level, Spot) from policy (pricing, assignment) and whether you reach for enums vs polymorphism appropriately. Strong candidates push back on subclassing Vehicle for behavior it doesn't have, and they put concurrency front and center the moment 'allocate a spot' is mentioned. Weak answers couple pricing into ParkingLot and end up rewriting the world when surge pricing arrives.