Low-level design · Booking and reservations
Parking lot system
- Reported in several places
- SDE-3 (L6); also SDE-1 (L4)
- Strategy
- Factory
- 2 sources
Two or more independent public pages, at least one a candidate write-up.
Model a parking lot that has small, medium and large spots. It must let you check whether a spot is available and respect which vehicle types may use which spots. Expect to discuss how the design grows to handle real-world complications.
Restated in our own words, not a quote from Amazon or any page. The answer below is written for this site.
Clarify first
Pin the scope down before you name a single class.
Questions worth asking before drawing anything:
- Is this one lot with one entrance, or several floors and gates?
- Which vehicle types exist, and may a small vehicle use a larger spot?
- How is the fee calculated, and who pays on exit?
- Do spots ever go out of service, and is a reservation a requirement now or a later extension?
Assumed scope. The reports name three spot sizes, an availability check, vehicle-type constraints and unspecified real-world extensions. Tickets, fees, out-of-service spots, hourly pricing and reservations are this answer's own assumptions and extension examples, not reported requirements. In: one lot, three spot sizes (small, medium, large), three vehicle types (bike, car, truck), ticket on entry, fee on exit, an availability count per size, and a swappable spot-picking rule and pricing rule. Out: payment processing, entry hardware, multiple lots, and reservations, which are discussed as an extension.
Entities and relationships
Who owns what, and who talks to whom.
- Vehicle has a plate and a type. It knows the smallest spot size it needs (bike needs small, car needs medium, truck needs large).
- Spot has an id, a size, a distance from the entrance, an occupant and an out-of-service flag. It answers
isFreeandfits(vehicle). - SmallestFit and NearestFit implement the allocation strategy: given the spots and a vehicle, return one free spot or nothing.
- HourlyPricing and FlatPricing implement the pricing strategy: given a spot and the minutes parked, return a fee.
- Ticket links a vehicle, the spot it took and the entry time.
- ParkingLot has many spots, uses one allocation strategy and one pricing strategy, owns the open tickets, and reads time from an injected clock.
The design
The class map, the main flow and the patterns that earn their place.
- A gate asks the lot to park a vehicle or to release a ticket.
- The lot asks the allocation strategy for one free spot that fits the vehicle.
- It marks the spot occupied and issues a ticket with the entry time.
- On exit the pricing strategy turns the minutes parked into a fee.
Open on the whiteboardAll 2 diagrams on one tidy board, with a step-by-step walkthrough.
The lot is a thin coordinator. It owns the spots and the open tickets, and it delegates the two decisions that tend to change: which spot to give, and how much to charge. Both are strategies passed into the constructor, so adding a new rule never edits ParkingLot.
Entry flow. park first rejects a plate that already has an open ticket. It then asks the allocation strategy for a spot. SmallestFit scans every spot and keeps the free one that fits with the smallest size, breaking ties by the shortest distance, so large spots stay available for trucks. NearestFit ignores size and takes the closest free spot that fits. If the strategy returns nothing, the lot answers with a reason instead of throwing, because a full lot is a normal outcome. Otherwise it marks the spot occupied, builds a Ticket with the clock reading and stores it.
Exit flow. unpark looks up the ticket, computes the minutes from the injected clock, asks the pricing strategy for the fee, frees the spot and deletes the ticket. HourlyPricing charges every started hour with a one hour minimum, so 60 minutes costs one hour and 61 costs two. Using a clock object rather than reading the system time is what lets the tests step time deterministically.
Where patterns earn their place. Strategy appears twice and each use is a real axis of change. The small buildLot function plays the factory role: it builds the spot layout and wires the lot together, so callers never assemble spots by hand. A singleton is not needed: one lot instance is passed where it is used.
Complexity and trade-off. Allocation is a linear scan of the spots, and the duplicate plate check scans open tickets. That is fine for a few hundred spots and keeps the code obvious. For thousands, keep a free list per size ordered by distance (a heap or sorted set) and a map from plate to ticket, which makes both operations logarithmic or constant. In a concurrent lot, park must claim the spot atomically (compare and set on the occupant), otherwise two gates can hand out the same spot.
The code
Runnable JavaScript, with the checks it passes.
const SIZE_RANK = { SMALL: 1, MEDIUM: 2, LARGE: 3 };
const NEEDS = { BIKE: "SMALL", CAR: "MEDIUM", TRUCK: "LARGE" };
class Vehicle {
constructor(plate, type) {
if (!NEEDS[type]) throw new Error("unknown vehicle type " + type);
this.plate = plate;
this.type = type;
}
get minimumSize() {
return NEEDS[this.type];
}
}
class Spot {
constructor(id, size, distance) {
this.id = id;
this.size = size;
this.distance = distance;
this.occupant = null;
this.outOfService = false;
}
get isFree() {
return this.occupant === null && !this.outOfService;
}
fits(vehicle) {
return SIZE_RANK[this.size] >= SIZE_RANK[vehicle.minimumSize];
}
}
class SmallestFit {
pick(spots, vehicle) {
let best = null;
for (const spot of spots) {
if (!spot.isFree || !spot.fits(vehicle)) continue;
const smaller = best && SIZE_RANK[spot.size] < SIZE_RANK[best.size];
const closer = best && spot.size === best.size && spot.distance < best.distance;
if (!best || smaller || closer) best = spot;
}
return best;
}
}
class NearestFit {
pick(spots, vehicle) {
let best = null;
for (const spot of spots) {
if (!spot.isFree || !spot.fits(vehicle)) continue;
if (!best || spot.distance < best.distance) best = spot;
}
return best;
}
}
class HourlyPricing {
constructor(ratesBySize) {
this.rates = ratesBySize;
}
fee(spot, minutes) {
const hours = Math.max(1, Math.ceil(minutes / 60));
return hours * this.rates[spot.size];
}
}
class FlatPricing {
constructor(amount) {
this.amount = amount;
}
fee() {
return this.amount;
}
}
class Ticket {
constructor(id, vehicle, spot, enteredAt) {
this.id = id;
this.vehicle = vehicle;
this.spot = spot;
this.enteredAt = enteredAt;
}
}
class ParkingLot {
constructor(spots, allocation, pricing, clock) {
this.spots = spots;
this.allocation = allocation;
this.pricing = pricing;
this.clock = clock;
this.open = new Map();
this.nextTicket = 1;
}
park(vehicle) {
for (const ticket of this.open.values()) {
if (ticket.vehicle.plate === vehicle.plate) return { ok: false, reason: "already parked" };
}
const spot = this.allocation.pick(this.spots, vehicle);
if (!spot) return { ok: false, reason: "no spot available" };
spot.occupant = vehicle;
const ticket = new Ticket("T" + this.nextTicket++, vehicle, spot, this.clock.now());
this.open.set(ticket.id, ticket);
return { ok: true, ticket };
}
unpark(ticketId) {
const ticket = this.open.get(ticketId);
if (!ticket) return { ok: false, reason: "unknown ticket" };
const minutes = this.clock.now() - ticket.enteredAt;
const fee = this.pricing.fee(ticket.spot, minutes);
ticket.spot.occupant = null;
this.open.delete(ticketId);
return { ok: true, fee, spotId: ticket.spot.id, minutes };
}
setOutOfService(spotId, value) {
const spot = this.spots.find((s) => s.id === spotId);
if (!spot || spot.occupant) return false;
spot.outOfService = value;
return true;
}
availability() {
const counts = { SMALL: 0, MEDIUM: 0, LARGE: 0 };
for (const spot of this.spots) if (spot.isFree) counts[spot.size]++;
return counts;
}
setPricing(pricing) {
this.pricing = pricing;
}
}
class FakeClock {
constructor(start = 0) {
this.time = start;
}
now() {
return this.time;
}
advance(minutes) {
this.time += minutes;
}
}
function buildLot(allocation, pricing, clock) {
const spots = [
new Spot("S1", "SMALL", 5),
new Spot("S2", "SMALL", 4),
new Spot("M1", "MEDIUM", 3),
new Spot("M2", "MEDIUM", 2),
new Spot("L1", "LARGE", 1),
];
return new ParkingLot(spots, allocation, pricing, clock);
}Runs here in your browser, in a sandboxed worker. Nothing is sent anywhere.
Try your own input
Write JavaScript that calls the code above and prints with console.log. It runs after the solution.
Extend it
The change requests an interviewer throws next.
- How would you add reservations so a spot is held for a time window and the allocation strategy never gives it away?
- How would you support several floors, and let the allocation strategy prefer a floor near the exit that the driver picked?
- How would you price differently by vehicle type, by time of day, or with a daily cap, without growing one big if statement?
- What changes if two entry gates call park at the same moment, and where do you put the lock or the atomic claim?
- How would you let a car take two adjacent small spots when no medium spot is left?
Where candidates slip
The mistakes this problem punishes.
These are the mistakes this problem punishes:
- Branching on the vehicle type inside the lot, so every new vehicle type or pricing rule edits the core class. Push the rule into the vehicle needs and the strategies.
- Letting a truck into a medium spot, or a car into a small one. The fit check must compare ranked sizes, not just equality.
- Reading
Date.now()inside the lot, which makes pricing impossible to test. Inject a clock. - Charging zero for a short stay, or rounding 61 minutes down to one hour. State the rounding rule and test the boundary.
- Forgetting to free the spot on exit, or allowing the same plate to park twice.
- Starting with a deep inheritance tree of Car, Bike and Truck subclasses that differ only by a constant.
Have a go before you read.
Interviews reward the sketch you make cold. Clarify out loud, draw it, then compare with the answer.
Now make it stick
Where this was reported
Public pages only. Companies do not confirm these, and they rotate their questions, so treat each link as one person's account or one list's tag.