🧠 Parking Lot LLD — Thought Process Guide¶
Goal: Learn how to think when designing a Low-Level Design, not just what the final code looks like. Walk through the mental steps, decision points, and trade-offs you should make.
📊 Class Diagram¶
classDiagram
direction TB
class ParkingLot {
+LOST_TICKET_PENALTY: Decimal
+name: str
-floors: List~ParkingFloor~
-active_by_plate: Dict~str, ParkingTicket~
-fee_calculator: FeeCalculator
-clock: Callable
-lock: Lock
+add_floor(floor: ParkingFloor) None
+find_available_spot(vehicle: Vehicle) Optional~ParkingSpot~
+park_vehicle(vehicle: Vehicle) ParkingTicket
+unpark_vehicle(ticket_id: str) Decimal
+unpark_lost_ticket(license_plate: str) Decimal
+get_ticket(ticket_id: str) Optional~ParkingTicket~
+available_count(spot_type: Optional~SpotType~) int
+show_available_spots() str
+set_fee_calculator(fee_calculator: FeeCalculator) None
}
class ParkingFloor {
+floor_number: int
-spots: Dict~str, ParkingSpot~
-free: Dict~SpotType, Dict~
+add_spot(spot: ParkingSpot) None
+find_spot(spot_id: str) Optional~ParkingSpot~
+get_available_spots(spot_type: Optional~SpotType~) List~ParkingSpot~
+available_count(spot_type: SpotType) int
+first_free(spot_type: SpotType) Optional~ParkingSpot~
+occupy(spot: ParkingSpot, vehicle: Vehicle) None
+release(spot: ParkingSpot) Vehicle
}
class ParkingSpot {
+spot_id: str
+floor: int
+spot_type: SpotType
+is_available: bool
+parked_vehicle: Optional~Vehicle~
+park(vehicle: Vehicle) None
+vacate() Vehicle
}
class Vehicle {
<<abstract>>
+license_plate: str
+vehicle_type: VehicleType
+get_required_spot_type()* SpotType
}
class Motorcycle
class Car
class Truck
class VehicleFactory {
+create_vehicle(vehicle_type: VehicleType, license_plate: str)$ Vehicle
+register_vehicle_type(vehicle_type: VehicleType, vehicle_class: Type)$ None
}
class SpotAllocationMapping {
+get_allowed_spots(vehicle: Vehicle)$ Tuple~SpotType~
}
class FeeCalculator {
<<abstract>>
+calculate_fee(duration: timedelta, spot_type: SpotType)* Decimal
}
class HourlyFeeCalculator {
+calculate_fee(duration: timedelta, spot_type: SpotType) Decimal
}
class DailyFeeCalculator {
+calculate_fee(duration: timedelta, spot_type: SpotType) Decimal
}
class TicketManager {
-tickets: Dict~str, ParkingTicket~
+create_ticket(spot: ParkingSpot, vehicle: Vehicle, entry_time: datetime) ParkingTicket
+get_ticket(ticket_id: str) Optional~ParkingTicket~
}
class ParkingTicket {
+ticket_id: str
+spot: ParkingSpot
+vehicle: Vehicle
+entry_time: datetime
+exit_time: Optional~datetime~
+fee: Optional~Decimal~
+status: ParkingTicketStatus
+close(exit_time: datetime, fee_calculator: FeeCalculator, penalty: Decimal, status: ParkingTicketStatus) Decimal
}
class DisplayBoard {
+render(floors: List~ParkingFloor~)$ str
}
class VehicleType {
<<enumeration>>
MOTORCYCLE
CAR
TRUCK
}
class SpotType {
<<enumeration>>
MOTORCYCLE
COMPACT
LARGE
+size: int
}
class ParkingTicketStatus {
<<enumeration>>
ACTIVE
PAID
LOST
}
ParkingLot "1" *-- "1..*" ParkingFloor : floors
ParkingFloor "1" *-- "*" ParkingSpot : spots
ParkingLot "1" *-- "1" TicketManager
TicketManager "1" *-- "*" ParkingTicket : issues
ParkingLot o-- FeeCalculator : pricing strategy
ParkingLot ..> SpotAllocationMapping : best-fit order
ParkingLot ..> DisplayBoard : renders
ParkingSpot "0..1" --> Vehicle : parked_vehicle
ParkingTicket --> ParkingSpot
ParkingTicket --> Vehicle
ParkingTicket ..> FeeCalculator : close()
ParkingTicket --> ParkingTicketStatus
ParkingSpot --> SpotType
Vehicle <|-- Motorcycle
Vehicle <|-- Car
Vehicle <|-- Truck
Vehicle --> VehicleType
Vehicle ..> SpotType : required spot
VehicleFactory ..> Vehicle : creates
FeeCalculator <|-- HourlyFeeCalculator
FeeCalculator <|-- DailyFeeCalculator
Table of Contents¶
- Phase 0: Requirements Gathering
- Phase 1: Identify the Nouns (Entities)
- Phase 2: Enums — Your First Building Block
- Phase 3: Which Classes Need Methods? (dataclass vs
__init__) - Phase 4: Assigning Responsibilities (Which Function Goes Where)
- Phase 5: Relationships & Composition
- Phase 6: Polymorphism & Inheritance
- Phase 7: Design Patterns — When & Why
- Phase 8: Review & Refine Checklist
- Phase 9: Concurrency — The Part Most Candidates Skip
- How to Run This in a 45–60 min Interview
Phase 0: Requirements Gathering¶
Before you write a single line of code, ask these questions:
| Question | Why It Matters | Example Answer |
|---|---|---|
| What vehicle types exist? | Determines your entity hierarchy | Motorcycle, Car, Truck |
| How many floors? | Composition: Lot → Floors → Spots | 2 floors |
| How is pricing calculated? | Determines your Strategy pattern | Hourly rates vary by spot type |
| Is it first-come-first-served or reservation-based? | Affects spot allocation logic | First-come-first-served |
| Do we need a display board? | Adds Observer-like behavior | Yes, show availability |
| Can a vehicle park in any spot? | Creates allocation rules | Motorcycles can use any spot |
| Best fit or nearest spot? | Decides the allocation loop order | Best fit: keep LARGE spots for trucks |
| Multiple entry gates at once? | Puts concurrency in scope | Yes: allocation must be race-free |
| What happens with a lost ticket? | Needs a lookup by plate | Fee so far + flat penalty |
| In-memory only, or persisted? | Decides how far to take the DB discussion | In-memory for the code, discuss DB after |
💡 Interview Tip: Always clarify scope before writing code. It shows you think about the problem before diving into the solution. Say: "Let me clarify a few things before I start — what vehicle types, how many floors, what pricing model?"
Phase 1: Identify the Nouns (Entities)¶
Take the requirements and circle every noun. These are your candidate classes.
"A parking lot has multiple floors. Each floor has parking spots of different sizes. A vehicle arrives, parks in a spot, gets a ticket, and pays a fee based on how long it stayed."
Nouns extracted: - Parking Lot - Floor - Parking Spot - Vehicle (Motorcycle, Car, Truck) - Ticket - Fee / Payment - Display Board
Thought Process:
"I see that 'Parking Lot' contains 'Floors', and 'Floors' contain 'Spots'. That's composition — a 'has-a' relationship. 'Vehicle' is a general thing with specific types — that's inheritance. 'Ticket' is created when a vehicle parks — that's an association."
Rule of Thumb: If a noun has state AND behavior, it's a class. If it only has fixed values, it's likely an Enum. If it's a passive data holder, consider a dataclass.
| Noun | Class or Enum? | Why |
|---|---|---|
| Vehicle Type | Enum | Fixed set of categories (MOTORCYCLE, CAR, TRUCK) |
| Spot Type | Enum | Fixed set of spot sizes |
| Ticket Status | Enum | Fixed lifecycle states (ACTIVE, PAID, LOST) |
| Vehicle | Abstract Class | Has data (license plate) + behavior (get_required_spot_type) |
| Parking Spot | Regular Class | Has state (available/occupied) + behavior (park, vacate) |
| Parking Ticket | Regular Class | Has state + behavior (close/calculate fee) |
| Fee Calculator | Interface/Abstract Class | Multiple implementations (hourly, daily) |
Phase 2: Enums — Your First Building Block¶
Always define Enums first. They are the vocabulary of your system. Every other class will reference them.
from enum import Enum
class VehicleType(Enum):
MOTORCYCLE = "Motorcycle"
CAR = "Car"
TRUCK = "Truck"
class SpotType(Enum):
MOTORCYCLE = "Motorcycle"
COMPACT = "Compact"
LARGE = "Large"
class ParkingTicketStatus(Enum):
ACTIVE = "Active"
PAID = "Paid"
LOST = "Lost"
Thought Process: Why Enums Instead of Strings or Constants?¶
| Approach | Problem | Why Enums Win |
|---|---|---|
Plain strings: "car", "truck" |
Typo risk: "cAr" vs "car" |
Enums are type-safe, no typos |
Constants: CAR = "car", TRUCK = "truck" |
No namespace — they pollute the module | Enums are self-namespaced: VehicleType.CAR |
Booleans: is_motorcycle=True |
Adding a 4th type breaks everything | Enums scale without breaking existing code |
💡 Key Insight: Enums are your type system within the type system. They let you write:
The type hints tell the reader exactly what values are valid.Phase 3: Which Classes Need Methods? (dataclass vs __init__)¶
This is one of the most important decisions in LLD. The answer depends on whether the class has behavior or is just passive data.
When to Use @dataclass¶
Use dataclass when the class is just a container for data with minimal or no logic.
from dataclasses import dataclass
@dataclass
class Address:
street: str
city: str
zip_code: str
@dataclass
class RateInfo:
spot_type: SpotType
hourly_rate: float
daily_max: float
Signs you need a dataclass:
- The class only holds data, no methods (or just __repr__ / display helpers)
- You want auto-generated __init__, __repr__, __eq__
- The class is used primarily as a return value from another method
- You need to compare instances by value, not identity
When to Use __init__¶
Use regular __init__ when the class has actual behavior or business logic.
class ParkingSpot:
def __init__(self, spot_id: str, floor: int, spot_type: SpotType):
self._spot_id = spot_id
self._floor = floor
self._spot_type = spot_type
self._is_available = True
self._parked_vehicle: Optional[Vehicle] = None
def park(self, vehicle: Vehicle) -> None: # Has behavior!
if not self._is_available:
raise ValueError("Already occupied")
self._parked_vehicle = vehicle
self._is_available = False
def vacate(self) -> Vehicle: # Has behavior!
...
Signs you need a regular class with __init__:
- The class has methods that change its state (park(), vacate(), close())
- You want to encapsulate internal state (private attributes with _)
- You need properties to control access (@property)
- The class participates in a design pattern (Strategy, Observer, etc.)
Decision Tree¶
Does the class only hold data?
├── YES → @dataclass (auto __init__, __eq__, __repr__)
└── NO → Is it a contract with several implementations?
├── YES → ABC + @abstractmethod
└── NO → Regular class with __init__ + methods
Applied to Parking Lot¶
# DATACLASS candidates (passive data):
# - RateInfo (just holds spot type + rates)
# - PaymentInfo (holds payment details, no behavior)
# (Money fields are Decimal, never float.)
# REGULAR CLASS candidates (has behavior + state):
# - ParkingSpot (park(), vacate()) ← STATE CHANGES
# - ParkingTicket (close()) ← STATE CHANGES
# - ParkingFloor (add_spot(), get_available_spots())
# - ParkingLot (park_vehicle(), unpark_vehicle())
# ABC candidates (contract/interface):
# - Vehicle (abstract, different types)
# - FeeCalculator (abstract, different strategies)
Phase 4: Assigning Responsibilities (Which Function Goes Where)¶
This is Single Responsibility Principle in action. For each function you identify, ask: "Which class owns this?"
Step 1: List all behaviors¶
From the requirements, extract every action:
| Action | Who Does It? | Why? |
|---|---|---|
| Park a vehicle in a spot | ParkingSpot.park() |
Spot owns its state |
| Vacate a spot | ParkingSpot.vacate() |
Spot owns its state |
| Find an available spot | ParkingLot.find_available_spot() |
Lot knows all floors and owns the lock |
| Create a ticket | TicketManager.create_ticket() |
TicketManager owns ticket lifecycle |
| Calculate fee | FeeCalculator.calculate_fee() |
FeeCalculator owns pricing logic |
| Close a ticket | ParkingTicket.close() |
Ticket owns its lifecycle |
| Show available spots | DisplayBoard.render() |
DisplayBoard owns output formatting |
| Determine allowed spots for a vehicle | SpotAllocationMapping.get_allowed_spots() |
Mapping owns allocation rules |
Step 2: Apply the "Why" Test¶
For each method you're about to write, ask: "Why does this method belong in this class?"
Method: park_vehicle(vehicle)
Class: ParkingLot
Q: Why does ParkingLot own this?
A: Because ParkingLot orchestrates the full flow:
1. Find a spot (asks each floor's free index)
2. Occupy it (ParkingFloor.occupy() -> ParkingSpot.park())
3. Create a ticket (delegates to TicketManager)
It's the Facade — the single entry point — and therefore
the natural owner of the lock that makes 1-3 atomic.
Method: close(exit_time, fee_calculator, ...)
Class: ParkingTicket
Q: Why does ParkingTicket own this?
A: Because closing a ticket means:
0. Refusing if it is not ACTIVE (ticket's own state machine)
1. Recording exit time (passed in from the lot's clock)
2. Calculating duration (ticket's own data)
3. Computing fee (delegates to FeeCalculator)
4. Updating status (ticket's own state)
= Everything related to the ticket's lifecycle lives HERE.
Step 3: The "Cohesion" Check¶
High cohesion = a class's methods all work toward a single purpose.
# ✅ GOOD: ParkingSpot — high cohesion
class ParkingSpot:
def park(self, vehicle) # related to spot occupancy
def vacate(self) # related to spot occupancy
# BOTH methods are about managing spot state
# ❌ BAD: ParkingSpot — low cohesion
class ParkingSpot:
def park(self, vehicle) # spot management
def calculate_fee(self, duration) # ❌ fee calculation? Not the spot's job!
def send_notification(self, msg) # ❌ notifications? Not the spot's job!
💡 Rule of Thumb: If you use "and" to describe what a class does, it probably has too many responsibilities. Example:
"ParkingSpot manages spot state and calculates fees and sends notifications" → ❌ Break it up!
Phase 5: Relationships & Composition¶
Has-A vs Is-A¶
This is the most critical relationship decision you'll make.
Composition (Has-A):
class ParkingLot:
def __init__(self):
self._floors: List[ParkingFloor] = [] # ParkingLot HAS floors
class ParkingFloor:
def __init__(self):
self._spots: Dict[str, ParkingSpot] = {} # Floor HAS spots
Inheritance (Is-A):
class Vehicle(ABC): # Base type
...
class Car(Vehicle): # Car IS-A Vehicle ✅
class Truck(Vehicle): # Truck IS-A Vehicle ✅
Thought Process: Composition vs Inheritance¶
| Scenario | Decision | Reasoning |
|---|---|---|
| Floor and Spot | Composition | A floor has spots. Spots don't exist without a floor. |
| Lot and Floor | Composition | A lot has floors. Floors don't exist without a lot. |
| Car and Vehicle | Inheritance | A car is a type of vehicle. Natural hierarchy. |
| FeeCalculator and HourlyFeeCalculator | Inheritance | HourlyFeeCalculator is a type of FeeCalculator. |
💡 Key Insight: If you can say "X IS-A Y", use inheritance. If you can say "X HAS-A Y", use composition. When in doubt, favor composition.
Drawing the Class Diagram¶
When you draw your class diagram, think about it layer by layer:
Layer 1 (Top): ParkingLot
┌──────────────────┼─────────────────────┐
Layer 2 (Middle): ParkingFloor TicketManager FeeCalculator (interface)
│ │ ┌──┴───────────────┐
Layer 3 (Bottom): ParkingSpot ParkingTicket HourlyFeeCalculator DailyFeeCalculator
│
Vehicle (abstract)
│
┌──────┼──────┐
Car Truck Motorcycle
For each arrow, ask: - Is this a solid line (composition) or dotted line (dependency)? - Does the child exist without the parent? (If yes, it's aggregation, not composition) - Is the dependency one-way? (It should be — avoid circular dependencies!)
Phase 6: Polymorphism & Inheritance¶
When you have behavior that varies by type, use polymorphism instead of conditionals.
❌ Bad: Type-checking with if-else¶
class Vehicle:
def __init__(self, vehicle_type: str):
self.type = vehicle_type
# Somewhere in the code:
if vehicle.type == "car":
spot_type = "compact"
elif vehicle.type == "truck":
spot_type = "large"
elif vehicle.type == "motorcycle":
spot_type = "motorcycle"
Problem: Every time you add a new vehicle type, you have to find and update every if-elif chain. This violates the Open/Closed Principle.
✅ Good: Polymorphism¶
class Vehicle(ABC):
@abstractmethod
def get_required_spot_type(self) -> SpotType:
pass
class Car(Vehicle):
def get_required_spot_type(self) -> SpotType:
return SpotType.COMPACT
class Truck(Vehicle):
def get_required_spot_type(self) -> SpotType:
return SpotType.LARGE
# No if-else needed anywhere!
spot_type = vehicle.get_required_spot_type()
Thought Process: When to Use Abstract Classes¶
Ask yourself: "Will there be multiple implementations of this behavior?"
| Question | If YES | If NO |
|---|---|---|
| Multiple fee calculation strategies? | Abstract FeeCalculator |
Concrete class is fine |
| Multiple vehicle types with different spot requirements? | Abstract Vehicle |
Single Vehicle class with enum |
| Multiple payment methods? | Abstract PaymentProcessor |
Single Payment class |
💡 Rule of Thumb: Don't over-abstract. Start with concrete classes, and extract an interface when you actually need a second implementation. Premature abstraction is as bad as no abstraction.
Phase 7: Design Patterns — When & Why¶
Don't force design patterns. They should emerge naturally from the design problems you encounter.
Pattern Discovery Process¶
Problem: "I need to create different types of vehicles"
→ Factory Pattern ✅ (VehicleFactory)
Problem: "I need to swap fee calculation strategies at runtime"
→ Strategy Pattern ✅ (FeeCalculator interface)
Problem: "I need a unified interface for the parking lot operations"
→ Facade Pattern ✅ (ParkingLot class)
Problem: "I need to notify the display board when spots change"
→ Observer only if there are several subscribers (boards, analytics,
mobile app). With one board, DisplayBoard.render() pulling counts is
simpler — that is what the code does.
Problem: "I need to enforce a lifecycle for tickets"
→ An enum + a guard in ParkingTicket.close() (ACTIVE → PAID | LOST).
The full State pattern earns its keep only when behaviour differs a
lot per state; two transitions do not justify it.
When NOT to Use a Pattern¶
| Pattern | When It's Overkill |
|---|---|
| Singleton | When you only need one instance but it's not critical. Just create one instance. |
| Factory | When you have only one vehicle type. Wait until you add a second. |
| Strategy | When you have only one pricing model. Add the interface when you need a second. |
| Observer | When you have only one subscriber. A direct call is simpler. |
💡 Rule of Thumb: YAGNI — You Ain't Gonna Need It. Don't add a pattern for a case that doesn't exist yet. Add it when you need it.
Phase 8: Review & Refine Checklist¶
After your first draft, run through this checklist:
🔲 SOLID Check¶
| Principle | Question | Parking Lot Example |
|---|---|---|
| Single Responsibility | Can each class be described in one sentence? | ParkingSpot → "Manages one spot's occupancy state" ✅ |
| Open/Closed | Can I add a new vehicle type without modifying existing classes? | Yes: new enum + new subclass + new mapping entry ✅ |
| Liskov Substitution | Can any Vehicle subclass be used wherever Vehicle is expected? | Car, Truck, Motorcycle all implement get_required_spot_type() ✅ |
| Interface Segregation | Are my interfaces minimal? | FeeCalculator has one method: calculate_fee(duration, spot_type) ✅ |
| Dependency Inversion | Do high-level classes depend on abstractions? | ParkingLot depends on FeeCalculator interface, not HourlyFeeCalculator ✅ |
🔲 Cohesion & Coupling Check¶
- High cohesion: Each class's methods are all about the same thing
- Low coupling: Classes interact through clean interfaces, not by accessing each other's internals
✅ ParkingSpot.park() → changes _is_available and _parked_vehicle (internal state)
❌ ParkingLot.park_vehicle() → directly accesses ParkingSpot._parked_vehicle (bad coupling!)
🔲 Encapsulation Check¶
Ask: "Are the internal details hidden?"
# ✅ GOOD: Private attribute with property
class ParkingSpot:
def __init__(self):
self._is_available = True # Private
@property
def is_available(self) -> bool:
return self._is_available # Read-only access
# ❌ BAD: Public attribute
class ParkingSpot:
def __init__(self):
self.is_available = True # Anyone can set this to False!
# What if someone sets is_available = False but doesn't park a vehicle?
# Now the system thinks a spot is occupied when it's not.
🔲 Naming Check¶
| Class | Good? | Why |
|---|---|---|
ParkingSpot |
✅ | Clear: it's a spot in the parking lot |
SpotAllocationMapping |
✅ | Clear: it maps vehicle types to allowed spots |
VehicleManager |
❌ | Too vague: manages what about vehicles? |
ParkingLotManager |
❌ | Too vague: "Manager" classes often become god classes |
ParkingLot |
✅ | Clear and specific |
💡 Rule of Thumb: If you're tempted to name a class XxxManager or XxxUtils, ask yourself: "What specific responsibility does this class have?" If you can't answer in one sentence, split it up.
🔲 The "New Feature" Test¶
Imagine adding a new feature and see how many classes need to change:
"Add an ElectricCar vehicle type that needs EV charging spots."
| Change Needed | Classes Modified |
|---|---|
| New enum value | VehicleType.EV → add to enum |
| New subclass | ElectricCar(Vehicle) → new class |
| New spot type | SpotType.EV + its size entry |
| Allocation rule | SpotAllocationMapping.get_allowed_spots() → EVs try EV spots first; non-EVs skip EV spots |
| New rate | HourlyFeeCalculator._rates → one new entry |
Result: a handful of additive changes concentrated in the enums, one new class and the one class that owns allocation rules. ParkingLot, ParkingFloor, ParkingTicket and the fee strategies' logic are untouched. Be honest that enums and the mapping are edited: OCP in practice means "changes land in the places designed for them", not literally zero edits.
Phase 9: Concurrency — The Part Most Candidates Skip¶
Multiple entry gates mean park_vehicle runs concurrently. The naive flow is a check-then-act race:
Gate A: find_available_spot() -> B01 Gate B: find_available_spot() -> B01
Gate A: B01.park(car1) Gate B: B01.park(car2) # boom, or silent overwrite
Fix: make find → occupy → issue ticket one critical section. In the code, ParkingLot holds a single threading.Lock, and park_vehicle, unpark_vehicle and unpark_lost_ticket each take it once. Say out loud:
- "Locking
ParkingSpot.park()alone isn't enough: the race is between choosing the spot and claiming it." - "One lock is fine here: the critical section is a few dict operations. I'd only stripe locks per floor or spot type if profiling showed contention, and then I'd have to lock in a fixed order to avoid deadlock."
- "Unpark takes the same lock so two exits racing on one ticket can't both charge."
- "Across processes this lock is useless; the DB becomes the arbiter (
FOR UPDATE SKIP LOCKEDor a conditionalUPDATE ... WHERE status='AVAILABLE')."
How to Run This in a 45–60 min Interview¶
| Time | Step | What you do | What you say out loud |
|---|---|---|---|
| 0–5 | Clarify | Ask the Phase 0 questions; write the agreed scope as 4–6 bullets | "I'll assume three vehicle sizes, multi-floor, hourly pricing, multiple gates. Reservations and payments are out of scope unless you want them." |
| 5–12 | Entities & interfaces | Enums, Vehicle ABC, ParkingSpot, ParkingFloor, ParkingTicket, FeeCalculator ABC, ParkingLot facade. Write signatures only. |
"Pricing changes most often, so it's a strategy. Allocation rules are their own class because the business will change them." |
| 12–35 | Core code | park_vehicle, unpark_vehicle, best-fit search, fee calc with Decimal and round-up, exceptions for full/duplicate/invalid ticket |
"I'm keeping a free-spot index per floor so I'm not scanning every spot on every entry." |
| 35–45 | Concurrency | Add the lock around find+occupy+ticket and around unpark; mention duplicate-plate check | "The race is check-then-act; here is the critical section." |
| 45–55 | Extension | Whatever they ask: EV spots, lost ticket, reservations, grace period. Show where it plugs in. | "This lands in SpotAllocationMapping / a FeeCalculator decorator; nothing else changes." |
| 55–60 | Test & wrap-up | Name the tests you'd write: best-fit order, full lot, double unpark, fee rounding with a fake clock, N-thread contention | "I injected the clock so fee tests don't sleep." |
If you're running behind: skip VehicleFactory and DisplayBoard entirely. They're the least interesting classes. Never skip the lock.
Summary: The Complete Thought Process Flow¶
┌─────────────────────────────────────────────────┐
│ 1. REQUIREMENTS GATHERING │
│ "What are the vehicle types, floors, │
│ pricing model?" │
└─────────────────────┬───────────────────────────┘
│
┌─────────────────────▼───────────────────────────┐
│ 2. IDENTIFY NOUNS │
│ Circle all nouns → candidate classes │
│ Circle all adjectives → candidate enums │
└─────────────────────┬───────────────────────────┘
│
┌─────────────────────▼───────────────────────────┐
│ 3. DEFINE ENUMS FIRST │
│ The vocabulary of your system │
└─────────────────────┬───────────────────────────┘
│
┌─────────────────────▼───────────────────────────┐
│ 4. CLASSIFY EACH ENTITY │
│ ├─ Pure data? → @dataclass │
│ ├─ Has behavior? → Regular __init__ │
│ └─ Has variants? → ABC + subclasses │
└─────────────────────┬───────────────────────────┘
│
┌─────────────────────▼───────────────────────────┐
│ 5. ASSIGN RESPONSIBILITIES │
│ For each function: Which class owns this? │
│ Check SRP: Can I describe in one sentence? │
└─────────────────────┬───────────────────────────┘
│
┌─────────────────────▼───────────────────────────┐
│ 6. DEFINE RELATIONSHIPS │
│ ├─ X HAS-A Y → composition │
│ └─ X IS-A Y → inheritance │
└─────────────────────┬───────────────────────────┘
│
┌─────────────────────▼───────────────────────────┐
│ 7. APPLY DESIGN PATTERNS (if needed) │
│ Don't force them — let them emerge │
└─────────────────────┬───────────────────────────┘
│
┌─────────────────────▼───────────────────────────┐
│ 8. REVIEW │
│ ├─ SOLID principles │
│ ├─ Cohesion & coupling │
│ ├─ Encapsulation │
│ └─ "New Feature" test │
└─────────────────────────────────────────────────┘
Quick Reference Card¶
| Decision | Rule |
|---|---|
| Enum vs String | Always use Enum for fixed categories |
| dataclass vs init | Use dataclass for passive data, init for behavior |
| Inheritance vs Composition | "IS-A" = inheritance, "HAS-A" = composition |
| Where to put a method | The class whose data it primarily uses |
| Abstract vs Concrete | Use ABC when you expect multiple implementations |
| Pattern or not | Don't add patterns for hypothetical futures |
| Private or Public | Default to private (_), expose via @property |
| One sentence test | If you can't describe a class in one sentence, it's doing too much |