Free lesson · Low Level Design Interviews
Step 2: Core Entities
Step 2 - Core Entities
by Olena Marchenko - Software Engineer II, Microsoft Azure Developer Tools.
The scope from Step 1 committed: multi-level facility with three levels, three vehicle types (Car, Bike, Truck) with named compatibility, four spot types, hourly pricing varied by spot type with a daily cap, concurrent gates, pluggable allocation strategy, no reservations or EV charging but with extension seams, payment abstracted. That's the input to modeling.
Modeling is where parking lot lives or dies. I cannot overstate this. In the case studies for Movie Booking and LRU Cache, the modeling phase generated 25-30% of the interview signal. For parking lot, it's closer to 35-40%. The problem's breadth means more modeling decisions; the polymorphism opportunities mean each decision has multiple defensible answers; the temptation to over-abstract or under-abstract is particularly strong. Strong candidates spend 12 minutes here - about a third of their remaining interview budget - and they spend it well.
This step is about the modeling decisions specific to parking lot. The vehicle hierarchy choice. The spot compatibility location. The ticket lifecycle. The capacity tracking design. The eight entities and how they relate. Each decision has named alternatives, and articulating the alternatives is the senior signal more than picking any specific option.
The Interviewer's Perspective
When the candidate transitions to modeling, my evaluation focuses on modeling discipline.
Did they enumerate the entities cleanly? Eight or so entities for the committed scope: ParkingLot, Level, ParkingSpot, Vehicle, ParkingTicket, PricingStrategy, SpotAllocationStrategy, PaymentProcessor. Plus the spot and vehicle subtypes if they go inheritance-heavy. Missing entities - no ticket, no strategy abstractions - is a junior signal. Adding spurious entities - User, ParkingEvent, SpotHistory - is also a signal in the wrong direction.
Did they handle the vehicle hierarchy deliberately? This is the canonical OO modeling decision on parking lot. Inheritance hierarchy (Vehicle abstract with Car, Bike, Truck concrete)? Enum-based (Vehicle concrete with VehicleType enum)? Composition (Vehicle with capability interfaces like Electrifiable, Oversized)? Each has a use case. Strong candidates evaluate the alternatives and pick deliberately.
Did they think about where compatibility logic lives? spot.canFit(vehicle) versus vehicle.fitsIn(spot) versus a separate CompatibilityChecker. The choice affects how easy it is to extend with new vehicle types and new spot types. Strong candidates surface the decision; weak ones implicitly default to one direction without considering the alternative.
Did they model the ticket lifecycle? Active, paid, closed, lost - these are states the ticket transitions through. Some candidates model this as an enum with validation; others extract a State pattern. Both are reasonable for the LLD scope; either earns its place over no lifecycle modeling at all.
Did they think about capacity tracking? Precomputed counters (maintained incrementally as spots change state) versus on-demand recompute (scan all spots). For a lot with thousands of spots, the difference matters. Strong candidates address it; weak ones don't notice the design choice.
Did they extract the Strategy interfaces? SpotAllocationStrategy and PricingStrategy. Both earn their place - the committed scope explicitly said pricing varies by spot type and allocation should be pluggable. Strong candidates surface both Strategies in modeling; weak ones treat them as implementation details.
Did they use the Lot → Level → Spot hierarchy cleanly? Lot owns Levels owns Spots. Navigation flows downward; queries can flow upward (Spot belongs to Level belongs to Lot). The relationships are aggregation, and articulating them clearly signals OO fluency.
The modeling phase generates about 35-40% of the interviewer's signal on parking lot. This is where the problem's depth lives.
Important: Parking lot's modeling phase is disproportionately consequential. Six modeling decisions - vehicle hierarchy, spot compatibility location, ticket lifecycle, capacity tracking, Strategy extractions, Lot/Level/Spot relationships - each have named alternatives. The senior candidate articulates the alternatives for each; the junior candidate picks one without consideration.
The Candidate's Perspective
The modeling phase for parking lot has specific shape:
Spend the time it deserves. Twelve minutes is appropriate. Don't rush to implementation.
Enumerate the entities first. Before diving into decisions, list out the entities you'll model. Roughly 8-10 for the committed scope. This gives the interviewer a roadmap of what's coming.
Make the polymorphism choices deliberate. Vehicle hierarchy and spot polymorphism are the two big polymorphism sites. Neither has a universally correct answer. Articulate the alternatives and commit.
Surface the Strategy abstractions explicitly. Don't bury them in implementation. Name them as entities: "PricingStrategy interface with implementations like HourlyByTypePricing, SimpleHourlyPricing, etc. SpotAllocationStrategy interface with implementations like FirstAvailableCompatible, NearestToEntrance, etc."
Articulate the ticket lifecycle. Even briefly. Active → paid → closed. Lost as an alternative state. Some lifecycle modeling earns its place.
Address capacity tracking design. "I'll maintain precomputed counters at the Level and Lot level for O(1) capacity queries. The counters update incrementally as spots change state under the lock."
Use the Lot → Level → Spot hierarchy. Articulate the navigation and the ownership.
The candidate's job is to produce a modeling articulation that's rich enough to support the rest of the interview - service layer, implementation, edge cases, extensions - without being so over-engineered that it produces noise.
Applied to Parking Lot
The eight entities for the committed scope, with the modeling decisions for each.
The Entity List
classDiagram
class ParkingLot {
-levels: List~Level~
-allocationStrategy: SpotAllocationStrategy
-pricingStrategy: PricingStrategy
-paymentProcessor: PaymentProcessor
-lock: ReentrantLock
+allocateSpot(Vehicle) Optional~Ticket~
+releaseSpot(Ticket) Receipt
+getAvailableCount(SpotType) int
}
class Level {
-levelNumber: int
-spots: List~ParkingSpot~
-availableCounts: Map~SpotType, Integer~
+findAvailable(Vehicle) Optional~Spot~
+decrement(SpotType)
+increment(SpotType)
}
class ParkingSpot {
-id: String
-spotType: SpotType
-status: SpotStatus
+canFit(Vehicle) boolean
+occupy(Vehicle)
+release()
}
class Vehicle {
<<abstract>>
-licensePlate: String
+vehicleType() VehicleType
}
class Car {
}
class Bike {
}
class Truck {
}
class ParkingTicket {
-ticketId: String
-vehicle: Vehicle
-spot: ParkingSpot
-entryTime: Instant
-status: TicketStatus
}
class PricingStrategy {
<<interface>>
+calculateFee(Ticket, Duration) Money
}
class SpotAllocationStrategy {
<<interface>>
+allocate(Lot, Vehicle) Optional~Spot~
}
ParkingLot "1" *-- "many" Level
Level "1" *-- "many" ParkingSpot
Vehicle <|-- Car
Vehicle <|-- Bike
Vehicle <|-- Truck
ParkingLot --> PricingStrategy
ParkingLot --> SpotAllocationStrategy
ParkingTicket --> Vehicle
ParkingTicket --> ParkingSpot
Eight entities. Three relationships. Two Strategy interfaces. One inheritance hierarchy. This is the working model.
Modeling Decision 1: Vehicle Hierarchy
The most consequential modeling decision. Three options:
Option A: Inheritance hierarchy.
abstract class Vehicle {
String licensePlate;
abstract VehicleType vehicleType();
}
class Car extends Vehicle { VehicleType vehicleType() { return CAR; } }
class Bike extends Vehicle { VehicleType vehicleType() { return BIKE; } }
class Truck extends Vehicle { VehicleType vehicleType() { return TRUCK; } }
Pros: Type-specific behavior is natural (Car can have car-specific methods). Polymorphism through method overriding. Each subtype is its own class with its own identity.
Cons: Rigid for cross-cutting characteristics. An "Electric Vehicle" could be a Car, Bike, or Truck. Where does it go in the hierarchy? Multiple-inheritance ambiguity. Adding a new vehicle type means adding a new class.
Option B: Enum-based.
class Vehicle {
String licensePlate;
VehicleType type; // enum: CAR, BIKE, TRUCK
}
enum VehicleType { CAR, BIKE, TRUCK }
Pros: Simple. Flexible for adding new types (just extend the enum). Single class to maintain.
Cons: Type-specific behavior becomes awkward - switch statements or if-else chains. Loses some polymorphism benefits.
Option C: Composition with capability interfaces.
class Vehicle {
String licensePlate;
Set<Capability> capabilities; // ELECTRIC, OVERSIZE, etc.
VehicleType type;
}
Pros: Handles cross-cutting characteristics elegantly (a vehicle can be ELECTRIC and OVERSIZE simultaneously). Most flexible.
Cons: Most complex. Capabilities feel over-engineered for the simple three-vehicle case.
Which to pick?
For the committed scope (three closed vehicle types, simple compatibility rules, no cross-cutting features in scope), Option A (inheritance) is usually correct. The hierarchy is shallow, the types are stable, and the inheritance gives clean polymorphic dispatch for any future type-specific behavior.
If the scope had included EV-charging (a cross-cutting capability), Option C would earn its place. If the scope had been many vehicle types without type-specific behavior, Option B would suffice.
The articulation: "Vehicle is an abstract class with concrete subtypes Car, Bike, Truck. The hierarchy is shallow and the types are closed for our scope. I considered enum-based - flexible but awkward for type-specific behavior. I considered composition with capabilities - most flexible but over-engineered for the simple three-type case. Inheritance is the right level of abstraction here. If we later add EV-charging as a cross-cutting capability, I'd extract a Chargeable interface and compose it onto specific vehicle types."
That articulation surfaces all three alternatives, names the trade-offs, commits with reasoning. Senior signal.
Modeling Decision 2: Spot Polymorphism
Similar question, different answer.
Option A: Subtype hierarchy.
abstract class ParkingSpot { ... }
class CompactSpot extends ParkingSpot { ... }
class RegularSpot extends ParkingSpot { ... }
class LargeSpot extends ParkingSpot { ... }
class HandicapSpot extends ParkingSpot { ... }
Option B: Enum-based.
class ParkingSpot {
SpotType type; // enum: COMPACT, REGULAR, LARGE, HANDICAP
SpotStatus status; // enum: AVAILABLE, OCCUPIED, OUT_OF_SERVICE
}
For spots, Option B (enum-based) is usually right. Spot subtypes differ mostly in which vehicle types they accept - that's encoded in the compatibility logic, not in per-subtype behavior. There's rarely a CompactSpot-specific method that doesn't apply to RegularSpot. The differences are in attribute values (type), not in behavior.
The articulation: "Spots use enum-based polymorphism, not inheritance. Spot subtypes differ in which vehicle types they accept, not in per-subtype behavior. The variation is data, not code. ParkingSpot is one concrete class with a SpotType enum field. If we later need spot-specific behavior - say, EV spots need charging-cable management - we'd reconsider."
This is a place where strong candidates surface a deliberately different modeling choice from vehicles. Showing that polymorphism approach depends on the nature of the variation, not a default rule, is senior signal.
Modeling Decision 3: Compatibility Location
Where does the "Bike fits in Compact" rule live?
Option A: On the Spot. spot.canFit(vehicle) - spots own their capacity rules.
Option B: On the Vehicle. vehicle.fitsIn(spot) - vehicles own their constraints.
Option C: Separate Compatibility table. compatibilityChecker.canFit(vehicle, spot) - extracted concern.
Option D: Method on both for double-dispatch. Complex, rarely used.
The discussion: Option A (spot.canFit(vehicle)) is the most common choice. Spots know what kinds of vehicles they accept. Adding a new spot type means adding a new spot class with its own canFit logic - the new code lives with the new type. Adding a new vehicle type might require updates to the existing spot logic, depending on whether existing spots need to accept it. Option B inverts this: spots are stable; vehicles know what they need.
For our committed scope (Bike fits anywhere; Car fits Regular and Large; Truck fits only Large), Option A is cleaner. The spot can check against the vehicle type with a simple lookup:
public boolean canFit(Vehicle vehicle) {
return switch (this.spotType) {
case COMPACT -> vehicle.vehicleType() == BIKE;
case REGULAR -> vehicle.vehicleType() == BIKE || vehicle.vehicleType() == CAR;
case LARGE -> true; // any vehicle
case HANDICAP -> vehicle.vehicleType() == CAR || vehicle.vehicleType() == BIKE;
};
}
If we extract a separate CompatibilityChecker later, the centralized rules become easier to externalize (e.g., load from configuration). But for the committed scope, embedding on the spot is clean.
The articulation: "Compatibility lives on the spot - spot.canFit(vehicle). Spots own their capacity rules. Adding a new spot type means adding a new spot's canFit logic - the change is localized. The alternative - vehicle.fitsIn(spot) - inverts this: vehicles know what they need. Both are defensible; spot-side feels cleaner because spots are the scarce resource we're allocating. If we later need to externalize the rules to configuration, we'd extract a CompatibilityChecker."
Modeling Decision 4: Ticket Lifecycle
What states does a ticket transition through?
States:
- Active - issued, vehicle is parked, not yet paid
- Paid - payment processed, ready for exit
- Closed - vehicle has exited, ticket is finalized
- Lost - vehicle is parked but ticket cannot be presented at exit (special handling)
- Cancelled - issued but cancelled before active use (rare)
Transitions:
- Active → Paid (on payment success)
- Active → Lost (on lost ticket declaration at exit)
- Paid → Closed (on exit barrier release)
- Lost → Closed (after lost-ticket fee paid)
Option A: Enum field with validation.
enum TicketStatus { ACTIVE, PAID, CLOSED, LOST }
class ParkingTicket {
TicketStatus status;
void transitionTo(TicketStatus next) {
if (!isValidTransition(this.status, next)) throw ...;
this.status = next;
}
}
Simple. The validation is in one place. For four states, this is plenty.
Option B: State pattern.
abstract class TicketState {
abstract TicketState onPayment();
abstract TicketState onExit();
}
class ActiveState extends TicketState { ... }
// etc.
Over-engineering for four states. State pattern earns its place when states have many state-specific operations.
For the committed scope, Option A is right. The articulation: "Tickets have four states - Active, Paid, Closed, Lost - with named transitions. I'll use an enum field with validation in a transitionTo method. State pattern would be over-engineered for four states; an enum with transition rules is the right level."
Modeling Decision 5: Capacity Tracking
How does the system know "how many compact spots are available on Level 2"?
Option A: Scan on demand. Loop through all spots, count available by type. O(n) per query.
Option B: Precomputed counters. Maintain availableCounts: Map<SpotType, Integer> on each Level. Increment on release, decrement on occupy.
For a lot with thousands of spots and frequent capacity queries (display boards refresh every few seconds), Option B is necessary. The counters update incrementally; queries are O(1).
The counters must be maintained atomically with the spot status changes. Under the lock, the sequence is: change spot status, update counter, release lock. If status changes without counter updates, the counter drifts and the display becomes unreliable.
The articulation: "Capacity tracking uses precomputed counters at the Level. Each Level has a Map<SpotType, Integer> of available counts. When a spot is occupied, we decrement the counter for that spot type; when released, we increment. The updates happen under the same lock as the status change, ensuring counter consistency. Queries are O(1). The alternative - scan on demand - is simpler but O(n), and display boards refresh often enough that O(n) at scale becomes expensive."
Modeling Decision 6: Strategy Extractions
Two Strategy interfaces, both earned by the committed scope.
SpotAllocationStrategy.
interface SpotAllocationStrategy {
Optional<ParkingSpot> allocate(ParkingLot lot, Vehicle vehicle);
}
class FirstAvailableCompatibleStrategy implements SpotAllocationStrategy { ... }
class NearestToEntranceStrategy implements SpotAllocationStrategy { ... }
class PreferredLevelStrategy implements SpotAllocationStrategy { ... }
PricingStrategy.
interface PricingStrategy {
Money calculateFee(ParkingTicket ticket, Duration stayDuration);
}
class HourlyByTypePricing implements PricingStrategy { ... }
class FlatRatePricing implements PricingStrategy { ... }
class TimeOfDayPricing implements PricingStrategy { ... }
Both interfaces injected into ParkingLot at construction. The default implementations cover the committed scope; alternative implementations are how extension happens.
The articulation: "Two Strategy interfaces. SpotAllocationStrategy abstracts the allocation logic - first-available-compatible is our default but the interface accepts nearest-to-entrance, preferred-level, others. PricingStrategy abstracts fee calculation - hourly-by-type is our default but can swap to flat-rate, time-of-day-variable, others. Both injected into ParkingLot at construction. This is the OCP design - adding a new strategy is a new class, not a modification to existing code."
The Lot → Level → Spot Navigation
The hierarchy:
flowchart TB
PL[ParkingLot]
L1[Level 1]
L2[Level 2]
L3[Level 3]
S1[Spot 1-A: REGULAR]
S2[Spot 1-B: COMPACT]
S3[Spot 1-C: LARGE]
S4[Spot 2-A: HANDICAP]
S5[Spot 2-B: REGULAR]
S6[Spot 3-A: LARGE]
AS[AllocationStrategy]
PS[PricingStrategy]
PP[PaymentProcessor]
PL --> L1
PL --> L2
PL --> L3
L1 --> S1
L1 --> S2
L1 --> S3
L2 --> S4
L2 --> S5
L3 --> S6
PL -.uses.-> AS
PL -.uses.-> PS
PL -.uses.-> PP
style PL fill:#a78bfa,stroke:#8b5cf6,color:#fff
style AS fill:#34d399,stroke:#10b981,color:#000
style PS fill:#34d399,stroke:#10b981,color:#000
style PP fill:#34d399,stroke:#10b981,color:#000
ParkingLot owns Levels. Each Level owns Spots. ParkingLot uses (composes with) the three Strategy/Processor interfaces. The composition relationships matter: lot is the orchestrator; levels and spots are the inventory; strategies are the policy.
Important: Six modeling decisions on parking lot, each with named alternatives. Vehicle hierarchy (inheritance preferred for shallow closed set). Spot polymorphism (enum-based preferred since variation is data not behavior). Compatibility location (on spot preferred). Ticket lifecycle (enum with validation). Capacity tracking (precomputed counters). Strategy extractions (two interfaces, both earned). Articulating each with its alternative is the senior signal.
The Conversation
What modeling actually sounds like in the interview room.
Strong Candidate
Interviewer: "Scope is locked. Move to modeling."
Candidate: "Parking lot has more modeling depth than most LLD problems, so I want to surface the decisions explicitly. About eight entities for our scope: ParkingLot, Level, ParkingSpot, Vehicle with three subtypes, ParkingTicket, PricingStrategy, SpotAllocationStrategy, and PaymentProcessor as an interface we don't implement.
Vehicle hierarchy first. Three options: inheritance with Vehicle abstract and Car/Bike/Truck concrete, enum-based with a single Vehicle class and a VehicleType enum, or composition with capability interfaces. For our scope - three closed vehicle types, shallow hierarchy, no cross-cutting capabilities like EV charging - inheritance is the right choice. If EV charging entered scope, I'd extract a Chargeable interface and compose it onto specific subtypes.
Spot polymorphism is different. Spot subtypes vary in which vehicle types they accept - that's data, not behavior. Enum-based with a SpotType field. One ParkingSpot class. If spot-specific behavior emerged later - EV spots managing charging cables - I'd reconsider.
Compatibility lives on the spot: spot.canFit(vehicle). Spots own their capacity rules. Adding new spot types localizes the change. The alternative - vehicle.fitsIn(spot) - inverts the dependency, also defensible. Spot-side feels cleaner because spots are the scarce resource."
Interviewer: "Ticket?"
Candidate: "Tickets have four states - Active, Paid, Closed, Lost - with named transitions. Active → Paid on payment success. Active → Lost on lost-ticket declaration. Paid → Closed on exit. Lost → Closed after lost-ticket fee. I'll use an enum field with validation in a transitionTo method. State pattern would be over-engineered for four states."
Interviewer: "Capacity tracking?"
Candidate: "Precomputed counters at the Level. Each Level has a Map<SpotType, Integer> of available counts. Decrement on occupy; increment on release. Updates happen under the same lock as the status change, ensuring consistency. Queries are O(1). The alternative - scan on demand - is simpler but O(n), too expensive at scale."
Interviewer: "Strategies?"
Candidate: "Two interfaces, both earned by the committed scope. SpotAllocationStrategy abstracts allocation logic - FirstAvailableCompatible is our default; alternatives like NearestToEntrance or PreferredLevel are how extension happens. PricingStrategy abstracts fee calculation - HourlyByTypePricing is our default; alternatives handle flat-rate, time-of-day-variable. Both injected into ParkingLot. OCP-friendly design: new strategies are new classes."
Interviewer: "Navigation?"
Candidate: "Lot owns Levels owns Spots. Composition. The Lot is the orchestrator; Levels and Spots are the inventory; Strategies are the policy. Tickets reference Vehicle and Spot for the active relationships. Clean directional ownership, no circular references."
About 11 minutes. Six modeling decisions articulated with alternatives. The interviewer hears: candidate has thought about the variations, named them, committed deliberately.
Weak Candidate
Interviewer: "Scope is locked. Move to modeling."
Candidate: "I'll have a Vehicle class with subclasses Car, Bike, Truck. A ParkingSpot class with a status enum. A ParkingLot class with a list of Levels, each with a list of ParkingSpots."
Interviewer: "What about pricing and allocation?"
Candidate: "PricingStrategy and AllocationStrategy interfaces."
Interviewer: "Where does compatibility live? Spot or vehicle?"
Candidate: "Hmm. On the spot, I think? spot.isCompatible(vehicle)."
Interviewer: "Why on the spot?"
Candidate: "Because... the spot has the type constraint?"
Interviewer: "What's the alternative?"
Candidate: "Vehicle could know what it fits in."
Interviewer: "Why is one better than the other?"
Candidate: "I think spot-side is more natural."
Interviewer: "Ticket lifecycle?"
Candidate: "Active, then closed when the vehicle leaves."
Interviewer: "What about lost tickets?"
Candidate: "Oh. There should be a lost state too."
Interviewer: "What about between active and closed? Payment?"
Candidate: "I guess... paid? So Active, Paid, Closed, Lost."
Interviewer: "How do you enforce that you can't go from Active straight to Closed without paying?"
Candidate: "I'd validate the transition in code."
Interviewer: "Where?"
Candidate: "In a method on the ticket."
Interviewer: "Capacity tracking? If I want to know how many compact spots are free on Level 2, how do I find out?"
Candidate: "I'd scan the spots on Level 2 and count."
Interviewer: "What's the complexity?"
Candidate: "O(n) per query."
Interviewer: "Acceptable at scale?"
Candidate: "Hmm. Maybe I should maintain counters?"
Interviewer: "Right. Continue."
About 11 minutes. Each modeling decision either incomplete or arrived-at through prompting. The candidate produced the same end-state structure as the strong candidate by the end, but the path was a sequence of corrections.
Strong vs Weak: Modeling Decisions
| Decision Point | Strong Candidate | Weak Candidate |
|---|---|---|
| Entity enumeration | 8 entities listed upfront | Discovered through dialogue |
| Vehicle hierarchy | 3 options articulated; chooses inheritance | Inheritance assumed; no alternative considered |
| Spot polymorphism | Enum-based articulated as different from vehicle | Subtype-by-default; not differentiated |
| Compatibility location | Spot-side with named alternative | "I think spot-side"; no rationale |
| Ticket lifecycle | 4 states, named transitions, enum-with-validation | 2 states initially; prompted to add Paid and Lost |
| Capacity tracking | Precomputed counters; scan alternative named | Scan first; prompted to consider counters |
| Strategy extractions | Both explicit; OCP framing | Mentioned but underweighted |
| Navigation | Composition with directional ownership | Implicit |
| Articulation pattern | "I'm choosing X over Y because Z" | Single choice, no rationale |
| Time used | 11 min producing 6 articulated decisions | 11 min producing 0-2 articulated decisions plus corrections |
The pattern: strong modeling has visible structure - six decisions, each with alternatives, each with rationale. Weak modeling has the same end-state but is built through interviewer-driven discovery.
Common Mistakes
The parking lot modeling failures I see most often:
Treating vehicle hierarchy as the only big decision. Spending all modeling time on Vehicle and skipping the other five (spot polymorphism, compatibility location, ticket lifecycle, capacity tracking, Strategies). Each is independently consequential.
Over-inheriting spots. Producing four spot subtypes (CompactSpot, RegularSpot, LargeSpot, HandicapSpot) when none have type-specific behavior. The variation is data, not code.
Skipping the ticket lifecycle. Treating ticket as a data record with no state semantics. Missing the lifecycle invariants the model should enforce.
Default to scan for capacity. Producing O(n) capacity queries because the candidate didn't think about query frequency vs state change frequency.
Underweighting the Strategy interfaces. Surfacing PricingStrategy and SpotAllocationStrategy as afterthoughts rather than as load-bearing OCP enablers.
Forgetting multi-stakeholder. Modeling only the driver flow. No consideration of administrator operations, even if out of scope.
Spurious entities. Adding User, SecurityEvent, ParkingHistory, AccessLog - entities the scope doesn't require.
Inheritance ambiguity unresolved. Picking inheritance for vehicles without articulating what happens when a vehicle has cross-cutting characteristics (EV that's also Oversize).
Warning: The most common modeling failure on parking lot is treating one modeling decision (usually vehicle hierarchy) as the entire modeling phase. Six decisions deserve articulation. Spending all time on one and rushing the rest produces an unbalanced design.
What "Good" Looks Like
Concrete signals I score positively:
Eight entities enumerated upfront. Before diving into decisions.
Vehicle hierarchy choice deliberate. Three options named; inheritance committed with rationale.
Spot polymorphism different from vehicle. Enum-based, with the differentiation explained ("variation is data, not behavior").
Compatibility location surfaced and chosen. Spot-side with the alternative named.
Ticket lifecycle modeled. Four states, transitions named, enum-with-validation chosen.
Capacity tracking design articulated. Precomputed counters with scan alternative named, scale reasoning.
Two Strategy interfaces surfaced. With OCP framing.
Lot → Level → Spot navigation explicit. Composition with directional ownership.
Multi-stakeholder mention. Even if briefly: "I'm modeling the driver and entry/exit operations; administrator flows would extend this."
Clean transition out. "With entities committed, let me move to class structure and the service layer."
A candidate hitting seven or more of these signals in 11-12 minutes has produced senior modeling work - the modeling-rich step that parking lot demands.
Try It
Exercise: Drill the modeling phase for parking lot:
Practice the eight-entity enumeration. From memory, in 60 seconds. Until it's reflexive.
Drill the six-decision articulation. Vehicle hierarchy, spot polymorphism, compatibility location, ticket lifecycle, capacity tracking, Strategy extractions. For each, practice "I'm choosing X over Y because Z."
Practice the differentiation between vehicle and spot polymorphism. Vehicles use inheritance; spots use enum. Articulate why this is deliberate, not arbitrary.
Drill the OCP framing for Strategies. "Adding a new pricing model is a new PricingStrategy class. The Cache doesn't change."
Time the modeling phase. Ten to twelve minutes producing all six articulations. Faster suggests skipping rationale; slower suggests over-detailing.
The next step (Step 3) covers classes and relationships - converting the modeling decisions into specific class definitions, method signatures, the orchestration layer, and the concurrency strategy. The modeling committed here becomes the input.