Documentation Index
Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
Use this file to discover all available pages before exploring further.
🎯 Problem Statement
Design a parking lot system that can:- Handle multiple floors and different vehicle types
- Track available and occupied spots
- Calculate parking fees based on duration
- Support multiple entry/exit points
📋 Step 1: Clarify Requirements
Questions to Ask the Interviewer
| Category | Question | Possible Answer |
|---|---|---|
| Vehicles | What types of vehicles do we support? | Cars, motorcycles, trucks |
| Spots | Are spot sizes different per vehicle type? | Yes - compact, regular, large |
| Floors | How many floors? Multiple entry/exit? | 3 floors, 2 entries, 2 exits |
| Pricing | Fixed rate or hourly? Different per vehicle? | Hourly, varies by vehicle type |
| Payment | What payment methods? | Cash, card, mobile |
| Features | Reservations? Electric charging? | Out of scope for now |
Functional Requirements
- Park vehicles of different types (car, motorcycle, truck)
- Assign the nearest available spot to a vehicle
- Calculate fees based on vehicle type and duration
- Support multiple payment methods
- Display available spots per floor
Non-Functional Requirements
- Handle concurrent entry/exit (thread safety)
- Fast spot lookup - O(1) for availability check
- Scalable to multiple parking lots
🧩 Step 2: Identify Core Objects
Vehicles
Parking Spots
Infrastructure
Entity-Responsibility Mapping
| Entity | Responsibilities |
|---|---|
Vehicle | Store type, license plate; check if fits in spot |
ParkingSpot | Track availability; assign/remove vehicles |
Floor | Manage spots on one floor; find available spot |
ParkingLot | Coordinate all floors; issue tickets |
ParkingTicket | Track entry time, spot; calculate fee |
PaymentMethod | Process payment (Strategy pattern) |
📐 Step 3: Class Diagram
💻 Step 4: Implementation
- Enums & Config
- Payment (Strategy)
▶️ Step 5: Usage Example
🎯 Key Design Decisions
Why Singleton for ParkingLot?
Why Singleton for ParkingLot?
- Consistent state across all entry/exit panels
- No duplicate instances wasting memory
- Single point of control for all operations
Why Strategy Pattern for Payment?
Why Strategy Pattern for Payment?
- Each payment method encapsulates its own logic
- Easy to A/B test payment methods
- Follows Open/Closed Principle
Why Thread Locking?
Why Thread Locking?
- Prevents race conditions when assigning spots
- Ensures ticket consistency
- Handles concurrent unpark operations
Why Vehicle Hierarchy?
Why Vehicle Hierarchy?
- Polymorphic behavior for
canFitIn()method - Easy addition of new vehicle types
- Type-safe spot assignment
VehicleType enum for simpler cases.🚀 Interview Extensions
Reserved/VIP Spots
Reserved/VIP Spots
Electric Vehicle Charging
Electric Vehicle Charging
Monthly Pass Holders
Monthly Pass Holders
Lost Ticket Handling
Lost Ticket Handling
Multi-Level Validation
Multi-Level Validation
📊 Complexity Analysis
| Operation | Time Complexity | Space Complexity |
|---|---|---|
parkVehicle() | O(F × S) | O(1) |
unparkVehicle() | O(1) | O(1) |
getAvailability() | O(F) | O(F) |
findAvailableSpot() | O(F × S) | O(1) |
✅ What Makes This Design Good?
| Principle | How It’s Applied |
|---|---|
| Single Responsibility | Each class has one job: Vehicle stores data, Spot manages allocation, Floor coordinates |
| Open/Closed | Add new payment methods without changing existing code |
| Liskov Substitution | Car, Motorcycle, Truck all substitute for Vehicle |
| Interface Segregation | PaymentMethod is focused, not a fat interface |
| Dependency Inversion | ParkingLot depends on abstract Vehicle, not concrete types |
Interview Deep-Dive Questions
Why did you choose Singleton for ParkingLot, and when would it backfire?
Why did you choose Singleton for ParkingLot, and when would it backfire?
- The Singleton guarantees a single source of truth for lot state — every entry gate, exit gate, and display panel references the same instance, which prevents phantom availability where two gates think the same spot is free.
- However, Singleton is one of the most over-applied patterns in LLD. The moment your company opens a second location, or you need to run integration tests in parallel, Singleton fights you. Each test shares mutable global state, so tests become order-dependent and flaky.
- In production I would keep the single-instance behavior but achieve it through dependency injection and a composition root, not by baking it into
__new__. Frameworks like Spring or Guice give you singleton scope without coupling your class to the pattern. - The real trade-off is testability vs. convenience. Singleton saves you from passing the instance around, but it hides a dependency, making the call graph harder to reason about and mock.
- Example: Imagine running 200 unit tests that all hit
ParkingLot._instance. One test parks a truck, the next test expects an empty lot — now you need manual teardown or areset()method, which itself is a code smell.
- If you replaced Singleton with dependency injection, walk me through how the entry gate, exit gate, and display panel would all receive the same ParkingLot instance.
- How would you redesign this if the system needed to manage a chain of 50 parking lots across a city, each with independent state but centralized reporting?
The park_vehicle method locks the entire ParkingLot. What concurrency problems does this create, and how would you fix them?
The park_vehicle method locks the entire ParkingLot. What concurrency problems does this create, and how would you fix them?
- A single coarse-grained lock means every park and unpark operation is serialized globally. If you have 4 entry gates and 4 exit gates, only one operation happens at a time — that is a throughput bottleneck when the morning rush hits and 200 cars arrive in 10 minutes.
- The first optimization is floor-level locking: each Floor gets its own lock. Two cars parking on different floors never contend. This alone can give you roughly N-times throughput where N is the number of floors.
- Going further, you could use spot-level compare-and-swap (CAS) with an atomic boolean for
is_available. The spot assignment becomes lock-free for the common case:if spot.is_available.compare_and_set(True, False)succeeds atomically. - You still need a lock (or optimistic concurrency) for the
vehicle_to_spotandactive_ticketsdictionaries, but those are short critical sections. AConcurrentHashMap(Java) orthreading.Lockscoped only to dict mutation keeps contention minimal. - The gotcha most people miss: the
_find_available_spotscan itself is a read path. If you lock during the scan, you block everyone. If you don’t lock, you get a TOCTOU race — a spot looks free during scan, but by the time you assign, someone else took it. The fix is optimistic assignment: scan without locking, attempt assignment with a CAS, and retry on failure.
- Walk me through the exact race condition that occurs if two threads simultaneously find the same spot available and both try to assign their vehicle.
- If you introduced floor-level locks, what happens when a car’s preferred floor is full and it cascades to the next floor — do you hold both locks simultaneously, and what deadlock risk does that introduce?
The PaymentProcessor calls unpark_vehicle and then processes payment. What happens if the payment fails after the vehicle is already unparked?
The PaymentProcessor calls unpark_vehicle and then processes payment. What happens if the payment fails after the vehicle is already unparked?
- This is a classic atomicity gap. The current code calls
unpark_vehiclewhich marks the ticket as PAID and frees the spot before payment actually succeeds. Ifpayment_method.process()returns False (card declined, network timeout), the vehicle is already unparked, the spot is freed, and the system has no record that money is owed. - The fix is to separate the concerns into phases: calculate fee (read-only), collect payment (external call), release vehicle (state mutation). Only after payment confirmation do you mutate state.
- In practice I would introduce a
TicketStatus.PENDING_PAYMENTstate. The flow becomes: (1) calculate fee and set status to PENDING_PAYMENT, (2) call payment gateway, (3) on success set PAID and free spot, (4) on failure revert to ACTIVE and keep the gate closed. - For idempotency, attach a unique payment reference ID to each attempt so retries do not double-charge. This matters because payment gateways can return ambiguous failures — the charge may have gone through but the response timed out.
- Example: Stripe recommends exactly this pattern — create a PaymentIntent, confirm it, and only fulfill (open the gate) on the
payment_intent.succeededwebhook, not on the synchronous API response.
- What if the payment succeeds but your system crashes before marking the ticket as PAID — how do you prevent the customer from being charged but unable to leave?
- How would you design a reconciliation process that detects and resolves mismatches between your parking system state and the payment gateway’s records?
A Car's can_fit_in method says it fits in COMPACT spots. Is that correct, and what design smell does the Vehicle hierarchy have?
A Car's can_fit_in method says it fits in COMPACT spots. Is that correct, and what design smell does the Vehicle hierarchy have?
- Looking at the code,
Car.can_fit_inreturns True for COMPACT, REGULAR, and LARGE. But COMPACT spots are semantically meant for motorcycles and small vehicles. In a real parking lot, a standard sedan does not fit in a compact motorcycle spot. This is a domain modeling error — the naming suggests size tiers, but the fitting logic does not enforce them consistently. - The deeper design smell is that fitting logic is scattered across vehicle subclasses instead of being centralized. Each Vehicle subclass hardcodes which SpotTypes it fits, which means adding a new SpotType (e.g., HANDICAPPED, EV_ONLY) requires modifying every Vehicle subclass — a direct violation of the Open/Closed Principle.
- A cleaner approach is a compatibility matrix or a size-based comparison: assign a numeric size to each VehicleType and each SpotType, then a vehicle fits if
vehicle.size <= spot.size. This reduces the fitting logic to a single comparison and makes it trivially extensible. - Alternatively, you could use a
FittingStrategythat encapsulates the rules and can be swapped or configured per parking lot — some lots may allow cars in compact spots (tight urban garages in Tokyo) while others do not. - Example: If the lot owner adds an EV_CHARGING spot type, with the current design you must add a case to Car, Motorcycle, and Truck. With a compatibility matrix, you add one row to a config table.
- How would you refactor the fitting logic so that adding a new VehicleType or SpotType requires zero changes to existing classes?
- In the current design,
can_fit_inlives on Vehicle andcan_fitlives on ParkingSpot and both check compatibility — who should own this decision, and what is the risk of having it in two places?
find_available_spot iterates all spots on all floors. How would you make spot lookup O(1) or O(log n)?
find_available_spot iterates all spots on all floors. How would you make spot lookup O(1) or O(log n)?
- The current implementation is O(F x S) where F is floors and S is spots per floor. For a 10-floor garage with 200 spots per floor, that is 2,000 iterations per park operation. Under the coarse lock, this means every car waits while the system scans 2,000 spots.
- Approach 1: Min-heap per spot type per floor. Maintain a heap of available spots keyed by spot ID (or proximity to entrance).
parkis O(log S) to pop the best spot;unparkis O(log S) to push the freed spot back. This is the classic answer and it works well. - Approach 2: Free-list with O(1) allocation. Maintain a set (or deque) of available spot IDs per
(floor, spot_type). Park pops from the set in O(1), unpark pushes back in O(1). You lose ordering (nearest spot), but gain constant time. - Approach 3: Bitmap per floor. Each floor has a bitfield where bit i represents spot i. Finding the first free spot is a single
ffs(find-first-set) CPU instruction on a 64-bit word, giving O(S/64) which is effectively O(1) for practical sizes. - The right choice depends on whether “nearest spot” matters. If drivers just want any spot quickly, the free-list wins. If the system should minimize walk distance, the heap wins.
- Example: Airport garages use the free-list approach — they do not care which specific spot you get, they just need to know “are there any spots left on level 3?” and assign one instantly. Shopping malls with “nearest to elevator” preference would use a heap.
- If you use a min-heap and two cars arrive simultaneously, how do you prevent both from being assigned the same “best” spot?
- The requirement says “assign the nearest available spot” — nearest to what? How does the definition of nearest change the data structure choice?
How would you add an EV charging feature without modifying any existing classes?
How would you add an EV charging feature without modifying any existing classes?
- This is a direct test of the Open/Closed Principle. The answer should demonstrate extension through new classes and composition, not by editing Vehicle, ParkingSpot, or ParkingLot.
- Step 1: Create
EVChargingSpotextendingParkingSpotwith additional fields:charger_type(Level 2, DC Fast),charging_rate_per_kwh,is_charging. Overridecan_fitto optionally check for EV capability on the vehicle. - Step 2: Create an
EVVehiclemixin or interface (e.g.,Chargeable) that Car or a newElectricCarclass implements. This avoids modifying the existingCarclass — you createElectricCar(Car, Chargeable)using multiple inheritance or composition. - Step 3: The fee calculation needs to account for charging. Instead of modifying
ParkingTicket.calculate_fee, use a Decorator pattern:ChargingFeeDecoratorwraps the base fee calculation and addskwh_used * rate. Or use a FeeStrategy that the ticket delegates to. - Step 4: The spot finder in Floor needs to route EVs to charging spots preferentially. This is where the current design creaks —
find_available_spotis tightly coupled toSpotTypeenum iteration order. A cleaner approach is aSpotAllocationStrategythat can be configured per vehicle type. - The honest caveat: In practice, you usually do need to touch the SpotType enum to add EV_CHARGING, unless you modeled spot capabilities as a set of tags from the start (e.g.,
capabilities: Set[str] = {"ev_charging", "covered", "handicapped"}). The tag-based approach is more extensible but adds complexity upfront.
ev_charging boolean to ParkingSpot” — this modifies an existing class for every new feature. Also a red flag: not mentioning fee calculation changes, which shows the candidate only thinks about the “happy path” of parking, not the full lifecycle.Follow-ups:- If EV charging spots can also be used by non-EV vehicles when the lot is full, how do you prioritize EVs without starving regular cars?
- Charging sessions can outlast parking sessions — a car might finish charging in 2 hours but stay parked for 8. How does this affect your ticket and billing model?
Walk me through what happens when the system has 1 spot left and 3 cars arrive at 3 different entry gates simultaneously.
Walk me through what happens when the system has 1 spot left and 3 cars arrive at 3 different entry gates simultaneously.
- With the current coarse lock on
park_vehicle, this is actually handled correctly but suboptimally. Thread 1 acquires the lock, finds the spot, assigns it, creates a ticket. Threads 2 and 3 block on the lock. When they eventually acquire it,_find_available_spotreturns None and they get back a None ticket, meaning “lot full.” - The problem is user experience. The driver at gate 2 waits for threads 1’s full park operation (spot scan + assignment + ticket creation) before being told “sorry, no spots.” A better design would have a fast availability check outside the lock — a simple atomic counter:
if available_count.get() <= 0: return Nonebefore even trying to acquire the lock. This turns away cars in O(1) when the lot is full. - There is also a fairness question. The current
threading.Lockin Python (CPython) does not guarantee FIFO ordering. Thread 3 might acquire the lock before Thread 2 even though Thread 2 arrived first. If fairness matters (it does at physical gates where drivers see each other), you need aqueue.Queueof pending requests processed in order. - At the physical level, a real system would have gate sensors and a reservation protocol: the gate sensor detects the car, reserves a spot tentatively via an API call, and only lifts the barrier on confirmation. If the reservation fails, the display shows “FULL” and the barrier stays down — no lock contention at the software level because the gate controller serializes per-lane.
- Example: Costco parking lots use induction loop sensors at each entrance. The central system decrements a counter on entry and increments on exit. When the counter hits zero, all entrance displays flip to “LOT FULL” simultaneously — no per-spot scanning needed.
- What if you want to support “soft reservations” — a driver gets assigned a spot via a mobile app 5 minutes before arrival. How do you handle the TTL on that reservation if they never show up?
- The atomic counter approach can drift if a crash happens between decrementing the counter and completing the park operation. How do you keep the counter consistent?