Design a Parking Lot
A parking lot maps directly onto physical objects — spots, vehicles, levels — each with persistent state and clear invariants, rather than the abstract logic of a game or a service: a real-world-entity problem. The design models those entities as classes with the right responsibilities, and considers what happens when two drivers arrive at the last open space at the same moment.
See It in Action
The demo below shows nearest-fitting-spot assignment: park a vehicle to see which spot is selected, and remove vehicles to free spots.
Clarifying Requirements
A good first move is to narrow the prompt with targeted questions. Does the lot have multiple levels? What vehicle types must it support? Is there a pricing component, or just assignment and release? What happens when the lot is full?
The functional requirements for this walkthrough: the lot has a fixed number of spots; each
spot has one of three sizes — small, medium, or large; a vehicle of a given size may park in
any spot that is the same size or larger (a medium vehicle fits a medium or large spot, but
not a small one); vehicles are identified by license plate; the park command assigns the
nearest fitting spot and returns a confirmation or "Full" when none exists; the leave
command releases the spot by plate and returns "Not found" if the plate is unknown; and a
free command reports the count of unoccupied spots.
The two key flows — parking and leaving — are captured below. Both start at the caller and end with a single string result.
Core Entities
Reading the requirements for nouns that carry state or enforce a rule gives a short list of candidates: a lot, a spot, a vehicle, and the notion of size. Not every noun earns a class — promote one only when it owns state or enforces a rule. Before reading on, decide which of these you would actually model as objects.
Decision checkpoint
Should Vehicle be its own class in this design?
The two nouns that clearly own state are the spot and the lot; size becomes a small value type.
VehicleSize and SpotSize are enumerations with three values each — small, medium,
large — ranked so the fit rule is a single integer comparison: a spot fits a vehicle when
spot.size.value >= vehicle_size.value. Keeping that rule on the spot rather than in the
caller is the "Tell, Don't Ask" principle: the spot decides whether it fits; no caller
inspects its size externally.
ParkingSpot owns one invariant: it holds at most one vehicle. It knows its index in the
lot (used for reporting), its size, and the plate of the vehicle currently parked. park and
leave are the only legal mutations — nothing else reaches in and writes the plate field
directly.
ParkingLot is the orchestrator and the only public interface. It holds the list of spots,
a dictionary from plate to spot for O(1) lookup on leave, and implements the three commands.
Finding the nearest fitting spot means scanning from index 0 and returning the first match —
a simple greedy assignment that keeps the algorithm inside ParkingLot and leaves ParkingSpot
free of search logic.
As the checkpoint anticipated, there is no Vehicle class: the size arrives as a string
argument, is mapped to the enum at the entry point, and the plate is stored on the spot once
parked. No behavior needs a Vehicle object of its own.
The entity map below shows how the three named things relate and which data structures connect them at runtime.
Decision checkpoint
A spot fits a vehicle when its size is at least the vehicle's. Should that comparison live in ParkingLot.park, or on ParkingSpot?
Designing the Classes
Step 3 of the delivery framework asks you to derive each class's state from specific requirements, then its behavior as method signatures — before writing a single line of implementation. The guiding principle is Tell, Don't Ask: a class owns decisions about its own data; callers never reach in and read fields to make decisions the class should make itself.
VehicleSize and SpotSize
The requirements mention three vehicle sizes and three spot sizes, each ranked so a fit check is an inequality. An enum with integer values encodes that ranking directly.
Its state is three named constants (SMALL = 1, MEDIUM = 2, LARGE = 3). The integer values are the ranking; the enum name is the label. Both enums are pure vocabulary — they carry no methods because no requirement asks a size to do anything, only to be compared.
The key design decision is keeping two separate enums rather than one shared Size enum.
VehicleSize and SpotSize represent different concepts even though their values mirror each
other today. If the requirements later change — a spot might gain a "compact" size that no
vehicle is — having separate types keeps the change local. Using one enum would silently couple
them.
ParkingSpot
The requirement "a spot holds at most one vehicle" is the defining invariant. Every field and
method on ParkingSpot exists to enforce or report that invariant.
Its state comes directly from the requirements: index (the spot's position in the lot, required
by the "Parked <plate> at spot <i>" output), size (the spot's SpotSize, required by the fit
rule), and plate (the license plate of the currently parked vehicle, or None when vacant — the
only field that changes after construction).
Its behavior follows from what each use-site needs: is_free() -> bool reports whether the spot is
vacant; ParkingLot.park calls this before assigning, so no caller reads plate directly.
fits(vehicle_size: VehicleSize) -> bool answers whether a vehicle of the given size may park here;
the rule self.size.value >= vehicle_size.value lives here, not in ParkingLot, because the spot
is the only entity that knows its own size. park(plate: str) -> None writes the plate, marking
the spot occupied. leave() -> None clears the plate, marking the spot vacant.
The fit rule placement deserves attention. If ParkingLot.park compared sizes itself
(if spot.size.value >= vehicle_size.value) it would violate Tell, Don't Ask — the caller
is reaching into the spot to read its size and make a decision the spot should make. Moving
fits onto ParkingSpot means the caller asks "does this spot fit?" and accepts yes or no,
never inspecting the raw size value.
Decision checkpoint
leave(plate) has to free the right spot. What state do you keep so it doesn't scan every spot looking for that plate?
ParkingLot
The requirements describe three commands — park, leave, free — all aimed at the lot as
a whole. ParkingLot is the facade: the only object the outside world speaks to.
Its state comes from the requirements: _spots: list[ParkingSpot] is the ordered collection of
all spots (ordering is required because "nearest" means lowest index), and _plate_to_spot: dict[str, ParkingSpot] is the index that makes leave O(1). Without the dict, releasing a spot
would require a linear scan of all spots to find the plate; the leave requirement ("release by
plate") directly demands this reverse lookup.
Its behavior follows from the three commands: park(vehicle_size: VehicleSize, plate: str) -> str
scans spots in index order, calls spot.is_free() and spot.fits(vehicle_size), assigns the first
match, and updates the plate index, returning the confirmation string or "Full". leave(plate: str) -> str looks up the plate in the index, tells the spot to leave, removes the plate from the
index, and returns the confirmation or "Not found". free_count() -> int counts spots where
is_free() is true.
The nearest-fitting-spot search lives entirely in ParkingLot.park. ParkingSpot is never
asked to find its neighbors or rank itself — it only answers questions about itself. This keeps
ParkingSpot simple and makes it easy to change the search strategy (e.g., prefer same-size
spots before larger ones) without touching ParkingSpot at all.
Class diagram
The public surface of ParkingLot is exactly three methods — one per command. Everything
else is private. A small public surface makes the design easy to test and safe to extend:
adding a pricing policy requires no change to ParkingSpot or the scan loop.
Try It Yourself
Before reading the implementation, build it yourself. Implement a function
parking_lot(num_spots, instructions) that replays a command stream and returns one result
line per command.
Spot sizes are assigned deterministically: spot i has size SpotSize((i % 3) + 1), so
spot 0 is SMALL, spot 1 is MEDIUM, spot 2 is LARGE, spot 3 is SMALL, and so on. This repeating
pattern ensures every size appears for any lot with three or more spots.
Each instruction is a list of strings. Three commands are supported:
["park", size, plate]— assign the lowest-indexed free spot whose size is ≥ the vehicle size; append"Parked <plate> at spot <i>", or"Full"if no fitting spot exists.["leave", plate]— free that vehicle's spot; append"<plate> left", or"Not found"if the plate is not currently parked.["free"]— append the count of unoccupied spots as a string.
Run your solution against the test cases. The reference implementation follows below.
Code Walkthrough
The reference design is two small classes and two enums. The implementation order follows
dependency: the enums and SIZE_MAP first, then ParkingSpot, then ParkingLot, then the
entry function that wires everything together.
Both enums assign integer values so the fit comparison is a single >= rather than a
lookup table. Python's Enum makes the integer available as .value.
SIZE_MAP is a module-level dictionary that converts the raw string from the command stream
("small", "medium", "large") to the typed enum at the entry point, so nothing deeper
in the stack ever handles a raw string size. Mapping at the boundary is a clean separation:
parsing lives at the edge, typed values flow inward.
1from enum import Enum
2
3
4class VehicleSize(Enum):
5 SMALL = 1
6 MEDIUM = 2
7 LARGE = 3
8
9
10class SpotSize(Enum):
11 SMALL = 1
12 MEDIUM = 2
13 LARGE = 3
14
15SIZE_MAP: dict[str, VehicleSize] = {
16 "small": VehicleSize.SMALL,
17 "medium": VehicleSize.MEDIUM,
18 "large": VehicleSize.LARGE,
19}
Run and Test
The exercise drives the design through a command stream that can be checked deterministically. Trace test 5 — size-fit logic — to see the contract.
Setup: 3 spots, so spot 0 is SMALL, spot 1 is MEDIUM, spot 2 is LARGE.
park medium M1 scans spots in order. Spot 0 is SMALL: fits(MEDIUM) is false (1 < 2). Spot
1 is MEDIUM: fits(MEDIUM) is true (2 ≥ 2) and the spot is free. Assign. Output:
"Parked M1 at spot 1".
park large L1 scans from spot 0. Spot 0 is SMALL (1 < 3, skip). Spot 1 is MEDIUM and
occupied (skip). Spot 2 is LARGE: fits(LARGE) is true (3 ≥ 3) and the spot is free.
Assign. Output: "Parked L1 at spot 2".
park medium M2 scans: spot 0 SMALL does not fit a medium vehicle; spot 1 is occupied; spot
2 is occupied. No fitting free spot found. Output: "Full".
free counts unoccupied spots: only spot 0 is free. Output: "1".
The full trace confirms that the fit rule (spot.size.value >= vehicle_size.value) correctly
keeps small spots available for small vehicles while blocking oversized vehicles.
The sequence below traces a single park medium M1 call step-by-step through the object
graph, showing exactly which method on which object runs at each moment.
Concurrency and Thread Safety
In a single-process, single-threaded context the design above is correct. Concurrency matters when two cars arrive at the last fitting spot simultaneously — a scenario that arises whenever the parking system runs on a server handling concurrent HTTP requests or incoming events from two lanes at the same time.
The double-allocation race
Without synchronization, two threads can both call park at the same moment. Thread 1
(Alice) scans the spots and finds spot 4 free and fitting. Before Alice's thread calls
spot.park(plate), the scheduler switches to Thread 2 (Bob). Bob's thread runs the same
scan, also finds spot 4 free and fitting, and calls spot.park("BOB"). The scheduler
switches back to Alice, who now calls spot.park("ALICE") — overwriting Bob. Both threads
return "Parked … at spot 4". Two drivers are directed to the same space.
The root cause is a check-then-act sequence that is not atomic: the check (is_free)
and the act (park) are two separate steps that another thread can interleave.
Fix: lock the critical section
The entire scan-and-assign sequence must run as a single atomic unit, wrapped in a lock that every parking operation shares:
1import threading
2
3class ParkingLot:
4 def __init__(self, num_spots: int) -> None:
5 # ... spot initialization unchanged ...
6 self._lock = threading.Lock()
7
8 def park(self, vehicle_size: VehicleSize, plate: str) -> str:
9 with self._lock: # acquired here
10 for spot in self._spots:
11 if spot.is_free() and spot.fits(vehicle_size):
12 spot.park(plate) # check and act are now atomic
13 self._plate_to_spot[plate] = spot
14 return f"Parked {plate} at spot {spot.index}"
15 return "Full"
16 # released here — Bob's thread now enters and finds the spot occupied
17
18 def leave(self, plate: str) -> str:
19 with self._lock:
20 spot = self._plate_to_spot.get(plate)
21 if spot is None:
22 return "Not found"
23 spot.leave()
24 del self._plate_to_spot[plate]
25 return f"{plate} left"
26
27 def free_count(self) -> int:
28 with self._lock:
29 return sum(1 for s in self._spots if s.is_free())
Locking only spot.park() is not enough — the race lives between the check and the act, and
both must be inside the same critical section.
Alternative: compare-and-set per spot
A single global lock serializes all parking operations even when two drivers are competing
for spots on different levels or in different sections. An alternative is a compare-and-set
(CAS) operation on each spot: ParkingSpot.park succeeds only if the plate field is
currently None, using threading.Lock per spot or an atomic compare-and-exchange. If the
CAS fails, the caller skips that spot and tries the next one.
This removes the global bottleneck and allows threads scanning non-overlapping sections to
proceed in parallel. The trade-off is that park may need to retry when a spot it found
available gets claimed before the CAS, which complicates the caller. For a single-level
parking lot the global lock is simpler and sufficient. For a high-throughput seat-reservation
or warehouse-slot system with many concurrent writers, lock striping — one lock per level
or per section — is the practical middle ground: threads on different levels never contend,
while the critical section on each level remains small.
Full Solution
1from enum import Enum
2
3# Spot size assignment: spot i has size SpotSize(i % 3):
4# i % 3 == 0 -> SMALL (value 1)
5# i % 3 == 1 -> MEDIUM (value 2)
6# i % 3 == 2 -> LARGE (value 3)
7# A vehicle of size S fits any spot with size >= S.
8
9class VehicleSize(Enum):
10 SMALL = 1
11 MEDIUM = 2
12 LARGE = 3
13
14
15class SpotSize(Enum):
16 SMALL = 1
17 MEDIUM = 2
18 LARGE = 3
19
20SIZE_MAP: dict[str, VehicleSize] = {
21 "small": VehicleSize.SMALL,
22 "medium": VehicleSize.MEDIUM,
23 "large": VehicleSize.LARGE,
24}
25
26
27class ParkingSpot:
28 def __init__(self, index: int, size: SpotSize) -> None:
29 self.index = index
30 self.size = size
31 self.plate: str | None = None
32
33 def is_free(self) -> bool:
34 return self.plate is None
35
36 def fits(self, vehicle_size: VehicleSize) -> bool:
37 return self.size.value >= vehicle_size.value
38
39 def park(self, plate: str) -> None:
40 self.plate = plate
41
42 def leave(self) -> None:
43 self.plate = None
44
45
46class ParkingLot:
47 def __init__(self, num_spots: int) -> None:
48 self._spots: list[ParkingSpot] = []
49 for i in range(num_spots):
50 size_val = (i % 3) + 1 # 1=SMALL, 2=MEDIUM, 3=LARGE
51 self._spots.append(ParkingSpot(i, SpotSize(size_val)))
52 self._plate_to_spot: dict[str, ParkingSpot] = {}
53
54 def park(self, vehicle_size: VehicleSize, plate: str) -> str:
55 for spot in self._spots:
56 if spot.is_free() and spot.fits(vehicle_size):
57 spot.park(plate)
58 self._plate_to_spot[plate] = spot
59 return f"Parked {plate} at spot {spot.index}"
60 return "Full"
61
62 def leave(self, plate: str) -> str:
63 spot = self._plate_to_spot.get(plate)
64 if spot is None:
65 return "Not found"
66 spot.leave()
67 del self._plate_to_spot[plate]
68 return f"{plate} left"
69
70 def free_count(self) -> int:
71 return sum(1 for s in self._spots if s.is_free())
72
73
74def parking_lot(num_spots: int, instructions: list[list[str]]) -> list[str]:
75 lot = ParkingLot(num_spots)
76 output: list[str] = []
77 for instruction in instructions:
78 command, *args = instruction
79 if command == "park":
80 size_str, plate = args[0], args[1]
81 vehicle_size = SIZE_MAP[size_str.lower()]
82 output.append(lot.park(vehicle_size, plate))
83 elif command == "leave":
84 plate = args[0]
85 output.append(lot.leave(plate))
86 elif command == "free":
87 output.append(str(lot.free_count()))
88 return output
89
90if __name__ == "__main__":
91 num_spots = int(input())
92 instructions = [input().split() for _ in range(int(input()))]
93 res = parking_lot(num_spots, instructions)
94 for line in res:
95 print(line)
961import java.util.ArrayList;
2import java.util.Arrays;
3import java.util.HashMap;
4import java.util.List;
5import java.util.Map;
6import java.util.Scanner;
7
8class Solution {
9 // Spot size: index % 3 == 0 -> SMALL(1), % 3 == 1 -> MEDIUM(2), % 3 == 2 -> LARGE(3)
10 // A vehicle of size S fits any spot with size >= S.
11
12 static class ParkingSpot {
13 int index;
14 int size; // 1=SMALL, 2=MEDIUM, 3=LARGE
15 String plate;
16
17 ParkingSpot(int index, int size) {
18 this.index = index;
19 this.size = size;
20 this.plate = null;
21 }
22
23 boolean isFree() { return plate == null; }
24 boolean fits(int vehicleSize) { return size >= vehicleSize; }
25 void park(String p) { plate = p; }
26 void leave() { plate = null; }
27 }
28
29 static class ParkingLotSystem {
30 List<ParkingSpot> spots = new ArrayList<>();
31 Map<String, ParkingSpot> plateToSpot = new HashMap<>();
32
33 ParkingLotSystem(int numSpots) {
34 for (int i = 0; i < numSpots; i++) {
35 spots.add(new ParkingSpot(i, (i % 3) + 1));
36 }
37 }
38
39 String park(int vehicleSize, String plate) {
40 for (ParkingSpot spot : spots) {
41 if (spot.isFree() && spot.fits(vehicleSize)) {
42 spot.park(plate);
43 plateToSpot.put(plate, spot);
44 return "Parked " + plate + " at spot " + spot.index;
45 }
46 }
47 return "Full";
48 }
49
50 String leave(String plate) {
51 ParkingSpot spot = plateToSpot.get(plate);
52 if (spot == null) return "Not found";
53 spot.leave();
54 plateToSpot.remove(plate);
55 return plate + " left";
56 }
57
58 int freeCount() {
59 int count = 0;
60 for (ParkingSpot s : spots) if (s.isFree()) count++;
61 return count;
62 }
63 }
64
65 static Map<String, Integer> SIZE_MAP = new HashMap<String, Integer>() {{
66 put("small", 1);
67 put("medium", 2);
68 put("large", 3);
69 }};
70
71 public static List<String> parkingLot(int numSpots, List<List<String>> instructions) {
72 ParkingLotSystem lot = new ParkingLotSystem(numSpots);
73 List<String> output = new ArrayList<>();
74 for (List<String> instruction : instructions) {
75 String command = instruction.get(0);
76 if (command.equals("park")) {
77 String sizeStr = instruction.get(1).toLowerCase();
78 String plate = instruction.get(2);
79 int vehicleSize = SIZE_MAP.get(sizeStr);
80 output.add(lot.park(vehicleSize, plate));
81 } else if (command.equals("leave")) {
82 String plate = instruction.get(1);
83 output.add(lot.leave(plate));
84 } else if (command.equals("free")) {
85 output.add(String.valueOf(lot.freeCount()));
86 }
87 }
88 return output;
89 }
90
91 public static List<String> splitWords(String s) {
92 return s.isEmpty() ? List.of() : Arrays.asList(s.split(" "));
93 }
94
95 public static void main(String[] args) {
96 java.util.Scanner scanner = new java.util.Scanner(System.in);
97 int numSpots = Integer.parseInt(scanner.nextLine());
98 int instructionsLength = Integer.parseInt(scanner.nextLine());
99 List<List<String>> instructions = new ArrayList<>();
100 for (int i = 0; i < instructionsLength; i++) {
101 instructions.add(splitWords(scanner.nextLine()));
102 }
103 scanner.close();
104 List<String> res = parkingLot(numSpots, instructions);
105 for (String line : res) {
106 System.out.println(line);
107 }
108 }
109}
1101"use strict";
2
3// Spot size: index % 3 == 0 -> SMALL(1), % 3 == 1 -> MEDIUM(2), % 3 == 2 -> LARGE(3)
4// A vehicle of size S fits any spot with size >= S.
5
6class ParkingSpot {
7 constructor(index, size) {
8 this.index = index;
9 this.size = size; // 1=SMALL, 2=MEDIUM, 3=LARGE
10 this.plate = null;
11 }
12 isFree() { return this.plate === null; }
13 fits(vehicleSize) { return this.size >= vehicleSize; }
14 park(plate) { this.plate = plate; }
15 leave() { this.plate = null; }
16}
17
18class ParkingLotSystem {
19 constructor(numSpots) {
20 this.spots = [];
21 for (let i = 0; i < numSpots; i++) {
22 this.spots.push(new ParkingSpot(i, (i % 3) + 1));
23 }
24 this.plateToSpot = new Map();
25 }
26 park(vehicleSize, plate) {
27 for (const spot of this.spots) {
28 if (spot.isFree() && spot.fits(vehicleSize)) {
29 spot.park(plate);
30 this.plateToSpot.set(plate, spot);
31 return `Parked ${plate} at spot ${spot.index}`;
32 }
33 }
34 return 'Full';
35 }
36 leave(plate) {
37 const spot = this.plateToSpot.get(plate);
38 if (spot === undefined) return 'Not found';
39 spot.leave();
40 this.plateToSpot.delete(plate);
41 return `${plate} left`;
42 }
43 freeCount() {
44 return this.spots.filter(s => s.isFree()).length;
45 }
46}
47
48const SIZE_MAP = { small: 1, medium: 2, large: 3 };
49
50function parkingLot(numSpots, instructions) {
51 const lot = new ParkingLotSystem(numSpots);
52 const output = [];
53 for (const instruction of instructions) {
54 const [command, ...args] = instruction;
55 if (command === 'park') {
56 const vehicleSize = SIZE_MAP[args[0].toLowerCase()];
57 const plate = args[1];
58 output.push(lot.park(vehicleSize, plate));
59 } else if (command === 'leave') {
60 output.push(lot.leave(args[0]));
61 } else if (command === 'free') {
62 output.push(String(lot.freeCount()));
63 }
64 }
65 return output;
66}
67
68function splitWords(s) {
69 return s === "" ? [] : s.split(" ");
70}
71
72function* main() {
73 const numSpots = parseInt(yield);
74 const instructionsLength = parseInt(yield);
75 const instructions = [];
76 for (let i = 0; i < instructionsLength; i++) {
77 instructions.push(splitWords(yield));
78 }
79 const res = parkingLot(numSpots, instructions);
80 for (const line of res) {
81 console.log(line);
82 }
83}
84
85class EOFError extends Error {}
86{
87 const gen = main();
88 const next = (line) => gen.next(line).done && process.exit();
89 let buf = "";
90 next();
91 process.stdin.setEncoding("utf8");
92 process.stdin.on("data", (data) => {
93 const lines = (buf + data).split("\n");
94 buf = lines.pop();
95 lines.forEach(next);
96 });
97 process.stdin.on("end", () => {
98 buf && next(buf);
99 gen.throw(new EOFError());
100 });
101}
1021// Spot size: index % 3 == 0 -> SMALL(1), % 3 == 1 -> MEDIUM(2), % 3 == 2 -> LARGE(3)
2// A vehicle of size S fits any spot with size >= S.
3
4class ParkingSpotTS {
5 index: number;
6 size: number; // 1=SMALL, 2=MEDIUM, 3=LARGE
7 plate: string | null;
8
9 constructor(index: number, size: number) {
10 this.index = index;
11 this.size = size;
12 this.plate = null;
13 }
14 isFree(): boolean { return this.plate === null; }
15 fits(vehicleSize: number): boolean { return this.size >= vehicleSize; }
16 park(plate: string): void { this.plate = plate; }
17 leave(): void { this.plate = null; }
18}
19
20class ParkingLotSystemTS {
21 spots: ParkingSpotTS[];
22 plateToSpot: Map<string, ParkingSpotTS>;
23
24 constructor(numSpots: number) {
25 this.spots = [];
26 for (let i = 0; i < numSpots; i++) {
27 this.spots.push(new ParkingSpotTS(i, (i % 3) + 1));
28 }
29 this.plateToSpot = new Map();
30 }
31 park(vehicleSize: number, plate: string): string {
32 for (const spot of this.spots) {
33 if (spot.isFree() && spot.fits(vehicleSize)) {
34 spot.park(plate);
35 this.plateToSpot.set(plate, spot);
36 return `Parked ${plate} at spot ${spot.index}`;
37 }
38 }
39 return 'Full';
40 }
41 leave(plate: string): string {
42 const spot = this.plateToSpot.get(plate);
43 if (spot === undefined) return 'Not found';
44 spot.leave();
45 this.plateToSpot.delete(plate);
46 return `${plate} left`;
47 }
48 freeCount(): number {
49 return this.spots.filter(s => s.isFree()).length;
50 }
51}
52
53const SIZE_MAP_TS: Record<string, number> = { small: 1, medium: 2, large: 3 };
54
55function parkingLot(numSpots: number, instructions: string[][]): string[] {
56 const lot = new ParkingLotSystemTS(numSpots);
57 const output: string[] = [];
58 for (const instruction of instructions) {
59 const [command, ...args] = instruction;
60 if (command === 'park') {
61 const vehicleSize = SIZE_MAP_TS[args[0].toLowerCase()];
62 const plate = args[1];
63 output.push(lot.park(vehicleSize, plate));
64 } else if (command === 'leave') {
65 output.push(lot.leave(args[0]));
66 } else if (command === 'free') {
67 output.push(String(lot.freeCount()));
68 }
69 }
70 return output;
71}
72
73function splitWords(s: string): string[] {
74 return s === "" ? [] : s.split(" ");
75}
76
77function* main() {
78 const numSpots = parseInt(yield);
79 const instructionsLength = parseInt(yield);
80 const instructions = [];
81 for (let i = 0; i < instructionsLength; i++) {
82 instructions.push(splitWords(yield));
83 }
84 const res = parkingLot(numSpots, instructions);
85 for (const line of res) {
86 console.log(line);
87 }
88}
89
90class EOFError extends Error {}
91{
92 const gen = main();
93 const next = (line?: string) => gen.next(line ?? "").done && process.exit();
94 let buf = "";
95 next();
96 process.stdin.setEncoding("utf8");
97 process.stdin.on("data", (data: string) => {
98 const lines = (buf + data).split("\n");
99 buf = lines.pop() ?? "";
100 lines.forEach(next);
101 });
102 process.stdin.on("end", () => {
103 buf && next(buf);
104 gen.throw(new EOFError());
105 });
106}
1071#include <algorithm>
2#include <iostream>
3#include <iterator>
4#include <limits>
5#include <sstream>
6#include <string>
7#include <vector>
8
9#include <string>
10#include <vector>
11#include <unordered_map>
12
13// Spot size: index % 3 == 0 -> SMALL(1), % 3 == 1 -> MEDIUM(2), % 3 == 2 -> LARGE(3)
14// A vehicle of size S fits any spot with size >= S.
15
16struct ParkingSpot {
17 int index;
18 int size; // 1=SMALL, 2=MEDIUM, 3=LARGE
19 std::string plate;
20
21 ParkingSpot(int idx, int sz) : index(idx), size(sz), plate("") {}
22 bool isFree() const { return plate.empty(); }
23 bool fits(int vehicleSize) const { return size >= vehicleSize; }
24 void park(const std::string& p) { plate = p; }
25 void leave() { plate = ""; }
26};
27
28struct ParkingLotSystem {
29 std::vector<ParkingSpot> spots;
30 std::unordered_map<std::string, int> plateToSpot; // plate -> spot index
31
32 ParkingLotSystem(int numSpots) {
33 for (int i = 0; i < numSpots; i++) {
34 spots.emplace_back(i, (i % 3) + 1);
35 }
36 }
37
38 std::string park(int vehicleSize, const std::string& plate) {
39 for (auto& spot : spots) {
40 if (spot.isFree() && spot.fits(vehicleSize)) {
41 spot.park(plate);
42 plateToSpot[plate] = spot.index;
43 return "Parked " + plate + " at spot " + std::to_string(spot.index);
44 }
45 }
46 return "Full";
47 }
48
49 std::string leave(const std::string& plate) {
50 auto it = plateToSpot.find(plate);
51 if (it == plateToSpot.end()) return "Not found";
52 int idx = it->second;
53 spots[idx].leave();
54 plateToSpot.erase(it);
55 return plate + " left";
56 }
57
58 int freeCount() const {
59 int count = 0;
60 for (const auto& s : spots) if (s.isFree()) count++;
61 return count;
62 }
63};
64
65static std::unordered_map<std::string, int> SIZE_MAP_PL = {
66 {"small", 1}, {"medium", 2}, {"large", 3}
67};
68
69std::string toLowerPL(std::string s) {
70 for (char& c : s) c = tolower(c);
71 return s;
72}
73
74std::vector<std::string> parking_lot(int num_spots, std::vector<std::vector<std::string>> instructions) {
75 ParkingLotSystem lot(num_spots);
76 std::vector<std::string> output;
77 for (const auto& instruction : instructions) {
78 const std::string& command = instruction[0];
79 if (command == "park") {
80 std::string sizeStr = toLowerPL(instruction[1]);
81 const std::string& plate = instruction[2];
82 int vehicleSize = SIZE_MAP_PL[sizeStr];
83 output.push_back(lot.park(vehicleSize, plate));
84 } else if (command == "leave") {
85 const std::string& plate = instruction[1];
86 output.push_back(lot.leave(plate));
87 } else if (command == "free") {
88 output.push_back(std::to_string(lot.freeCount()));
89 }
90 }
91 return output;
92}
93
94void ignore_line() {
95 std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
96}
97
98template<typename T>
99std::vector<T> get_words() {
100 std::string line;
101 std::getline(std::cin, line);
102 std::istringstream ss{line};
103 ss >> std::boolalpha;
104 std::vector<T> v;
105 std::copy(std::istream_iterator<T>{ss}, std::istream_iterator<T>{}, std::back_inserter(v));
106 return v;
107}
108
109int main() {
110 int num_spots;
111 std::cin >> num_spots;
112 ignore_line();
113 int instructions_length;
114 std::cin >> instructions_length;
115 ignore_line();
116 std::vector<std::vector<std::string>> instructions;
117 for (int i = 0; i < instructions_length; i++) {
118 instructions.emplace_back(get_words<std::string>());
119 }
120 std::vector<std::string> res = parking_lot(num_spots, instructions);
121 for (const std::string& line : res) {
122 std::cout << line << '\n';
123 }
124}
1251# Spot size: index % 3 == 0 -> SMALL(1), % 3 == 1 -> MEDIUM(2), % 3 == 2 -> LARGE(3)
2# A vehicle of size S fits any spot with size >= S.
3
4class ParkingSpotRB
5 attr_reader :index, :size
6 attr_accessor :plate
7
8 def initialize(index, size)
9 @index = index
10 @size = size
11 @plate = nil
12 end
13
14 def free?
15 @plate.nil?
16 end
17
18 def fits?(vehicle_size)
19 @size >= vehicle_size
20 end
21
22 def park(plate)
23 @plate = plate
24 end
25
26 def leave
27 @plate = nil
28 end
29end
30
31class ParkingLotSystemRB
32 def initialize(num_spots)
33 @spots = num_spots.times.map { |i| ParkingSpotRB.new(i, (i % 3) + 1) }
34 @plate_to_spot = {}
35 end
36
37 def park(vehicle_size, plate)
38 @spots.each do |spot|
39 if spot.free? && spot.fits?(vehicle_size)
40 spot.park(plate)
41 @plate_to_spot[plate] = spot
42 return "Parked #{plate} at spot #{spot.index}"
43 end
44 end
45 'Full'
46 end
47
48 def leave(plate)
49 spot = @plate_to_spot[plate]
50 return 'Not found' if spot.nil?
51 spot.leave
52 @plate_to_spot.delete(plate)
53 "#{plate} left"
54 end
55
56 def free_count
57 @spots.count(&:free?)
58 end
59end
60
61SIZE_MAP_PL = { 'small' => 1, 'medium' => 2, 'large' => 3 }
62
63def parking_lot(num_spots, instructions)
64 lot = ParkingLotSystemRB.new(num_spots)
65 output = []
66 instructions.each do |instruction|
67 command = instruction[0]
68 if command == 'park'
69 vehicle_size = SIZE_MAP_PL[instruction[1].downcase]
70 plate = instruction[2]
71 output << lot.park(vehicle_size, plate)
72 elsif command == 'leave'
73 output << lot.leave(instruction[1])
74 elsif command == 'free'
75 output << lot.free_count.to_s
76 end
77 end
78 output
79end
80
81if __FILE__ == $0
82 num_spots = Integer(gets, 10)
83 instructions = Array.new(Integer(gets, 10)) { gets.split }
84 res = parking_lot(num_spots, instructions)
85 res.each { |line| puts(line) }
86end
87Extensions
The same class structure supports common extensions.
Multiple levels add a Level class between ParkingLot and ParkingSpot. ParkingLot
iterates levels; each Level runs the same scan internally. The park and leave interfaces
do not change. Lock striping is natural here: one lock per level means unrelated levels never
wait on each other.
Pricing and tickets add a Ticket value object created at park time, carrying the spot and an
entry timestamp. leave accepts a ticket and passes it to a PricingCalculator that computes
the fee before releasing the spot. Neither ParkingSpot nor ParkingLot changes its core
responsibilities.
Spot reservations introduce a reserve command that holds a spot without parking a vehicle. A
status field on ParkingSpot — an enum of FREE, RESERVED, OCCUPIED — replaces the
boolean is_free. The fit rule and scan loop are unchanged; only the status transitions expand.
Each extension lands by adding a class or widening an enum, never by rewriting the core scan
and assignment logic. That stability follows from splitting responsibilities correctly at the
outset: ParkingSpot owns fit decisions, ParkingLot owns search and routing.
The multi-level extension illustrates this cleanly. A Level class slots between ParkingLot
and ParkingSpot; the public park and leave signatures on ParkingLot stay the same.
Quiz
Test your understanding of the key design decisions in this parking lot implementation.
Parking Lot Design Quiz
The size-fit rule (spot.size.value >= vehicle_size.value) lives on ParkingSpot.fits rather than inside ParkingLot.park. Why?
What requirement makes _plate_to_spot: dict[str, ParkingSpot] necessary in ParkingLot?
Why are spots stored in a list ordered by index, rather than a set or unordered collection?
Why does this design not introduce a Vehicle class to represent cars and trucks?
Two threads both call park for the last fitting spot at the same time. What goes wrong without synchronization, and what is the minimal fix?