Design Parking System: Simple Counter Pattern
LeetCode 1603. Design Parking System asks you to build a parking system that tracks three types of parking spaces: big, medium, and small. Each type has a fixed number of slots. When a car arrives, you check whether there is a slot available for its type. If yes, park it and return true. If not, return false.
Why this problem matters
Design Parking System is one of the simplest design problems on LeetCode, but it teaches a pattern you will use constantly: tracking finite resources with counters. Every time you manage a pool of items (database connections, API rate limits, inventory slots), you are doing the same thing. Count what is available, decrement when something is consumed, and reject when the count hits zero.
This problem also tests whether you can translate a real-world concept into clean code without overcomplicating it. There is no trick here. The value is in writing a solution that is short, correct, and impossible to misread.
The key insight
Each parking slot type is completely independent. A big car can never park in a medium or small slot. A small car can never park in a big slot. This means you do not need a shared pool or any cross-type logic. You just need three separate counters, one per slot type.
When addCar is called, you look at the counter for that car's type. If it is greater than zero, decrement it and return true. Otherwise return false. That is the entire algorithm.
The solution
class ParkingSystem:
def __init__(self, big: int, medium: int, small: int):
self.slots = [0, big, medium, small]
def addCar(self, carType: int) -> bool:
if self.slots[carType] > 0:
self.slots[carType] -= 1
return True
return False
The constructor stores the capacities in a list indexed by car type. Since car types are 1, 2, and 3, placing a dummy value at index 0 lets you use carType directly as the index. No if/elif chain, no dictionary lookup, just a single array access.
The addCar method checks whether the count for that type is positive. If so, it decrements the count (the car takes a slot) and returns True. If the count is zero, all slots of that type are taken, so it returns False.
Visual walkthrough
Let's trace through a sequence of operations on a parking system initialized with ParkingSystem(1, 1, 0). Pay attention to how each counter changes independently.
Step 1: ParkingSystem(1, 1, 0)
Create the system with 1 big slot, 1 medium slot, and 0 small slots.
Step 2: addCar(1) - Big car
Big slots: 0 used out of 1. There is room. Park the car and increment the counter to 1.
Step 3: addCar(2) - Medium car
Medium slots: 0 used out of 1. There is room. Park the car and increment the counter to 1.
Step 4: addCar(3) - Small car
Small slots: 0 out of 0. No small slots exist at all. The car cannot be parked.
Step 5: addCar(1) - Big car
Big slots: 1 used out of 1. All big slots are full. The car cannot be parked.
Notice that Step 4 returns False because the system was initialized with zero small slots. No amount of calling addCar(3) can succeed. Step 5 also returns False because the single big slot was already taken in Step 2. Each type is fully isolated from the others.
Complexity analysis
| Metric | Value |
|---|---|
| Time | O(1) per addCar call. One comparison and one decrement. |
| Space | O(1). The list stores exactly 4 integers regardless of how many cars arrive. |
The building blocks
This problem decomposes into two reusable pieces.
1. Array-indexed resource counters
Using the input value directly as an array index eliminates branching. Instead of writing if carType == 1 ... elif carType == 2 ..., you use self.slots[carType]. This pattern appears whenever you have a small, known set of categories mapped to integers. Frequency counting arrays, bucket sort, and lookup tables all use the same idea.
self.slots = [0, big, medium, small]
2. Decrement-and-check guard
The pattern of checking whether a counter is positive, then decrementing it, is a fundamental resource-management primitive. You will see it in problems like LRU Cache (checking capacity before inserting), rate limiters, and semaphore implementations. The guard prevents over-allocation.
if self.slots[carType] > 0:
self.slots[carType] -= 1
return True
return False
On CodeBricks, you drill each building block separately so that when they combine in a design problem like this, both pieces feel automatic.
Edge cases
- Zero capacity for a type:
ParkingSystem(0, 0, 5)means no big or medium slots exist. Any call toaddCar(1)oraddCar(2)must returnFalseimmediately. - All slots filled: after enough
addCarcalls, every type reaches zero. Subsequent calls for any type returnFalse. - Single slot per type:
ParkingSystem(1, 1, 1)means the first car of each type succeeds, and the second of any type fails. This tests that you decrement correctly on success. - Large capacities: the constraints allow up to 1000 slots per type, but since each operation is O(1), this does not affect performance.
From understanding to recall
You can read through this solution in under a minute and understand every line. But understanding is not the same as being able to implement it cleanly under interview pressure two weeks from now.
The gap between understanding and recall is where spaced repetition comes in. CodeBricks breaks Design Parking System into its building blocks (array-indexed counters, decrement-and-check guards) and drills them at increasing intervals. You type each piece from scratch, building real muscle memory. After a few review cycles, the counter pattern becomes automatic rather than something you have to rederive.
Related posts
- LRU Cache - A harder design problem that also manages fixed-capacity storage with eviction rules
- Min Stack - Another design problem where you track state alongside core operations
- Design Browser History - A design problem that exercises array or list-based state management