Production LLD
Designing for extension
The parking lot already works. Then the interviewer adds electric-vehicle (EV) spots, airport pricing, and a third gate. Candidates who pass had already left a : an interface or the host calls by name, so those sentences become a new class instead of a boolean. Candidates who fail reopen ParkingLot and add flags.
Extensibility is not "I used a pattern." It is predicting the follow-up and leaving that plug point before it arrives. Count the files you would touch when EV shows up; the panel on the right is the score.
What actually happens when the follow-up arrives
The story above is the default machine-coding move, not a trick question. The first prompt is a working lot. The second prompt changes the rule: prefer EV bays, charge airport tariffs, or open another gate. If park() already switches on vehicle type and site flags, that sentence lands in the same method, then in exit, receipt, and the printer. That scatter is shotgun surgery: one feature touching many files.
is the habit that stops the scatter. You add a type or a ; you do not reopen the switch that already works. is the same habit under its GRASP name (General Responsibility Assignment Software Patterns): hide the unstable algorithm behind a stable interface, and leave Ticket and Floor as ordinary classes.
In simpler terms: name the algorithm that will change, hide it behind an interface, and inject it at construction. When the follow-up arrives, you do not reopen park(). Do not invent a one-implementation wrapper for a type that will never vary. The next section is how you decide whether that interface is worth drawing now.
Simple rule of thumb: leave a seam, or keep a method?
Once you can name what will change, (You Aren't Gonna Need It) decides whether to hide it now or wait. The rule is not "no interface ever." It is "do not abstract a rule that has one form and no hint it will change."
Leave a seam
Parking Lot, Notification, Rule Engine, Splitwise. The interviewer will change the rule, so leave a on purpose.
A method is enough
One fee formula, no hint of a second. Keep it as a private method. Extract when the second formula appears, not before.
Protect the unstable
Hide time, ids, persistence, and vendor SDKs (third-party libraries) too. Those are seams for tests as much as for product follow-ups.
Variation map
What will change, and who absorbs it?
The rule of thumb above is in practice: add a type or a . Do not reopen the switch. (the GRASP name) is the same habit. The lot talks to ports (interfaces it calls by name). New rules arrive as implementations.
Read the diagram definition
flowchart LR
Lot[ParkingLot]
Alloc[SpotAllocationStrategy]
Price[PricingStrategy]
Notify[NotificationChannel]
Lot --> Alloc
Lot --> Price
Lot --> Notify
Alloc --> Near[Nearest]
Alloc --> Ev[EV preferred]
Price --> Hourly[Hourly]
Price --> Airport[Airport]
Notify --> Sms[SMS]
Notify --> Mail[Email]Three ports on the lot. Each follow-up below is a new class behind one of them, not a new branch in park().
SpotAllocationStrategy
Allocation
EV preferred, nearest, random, or handicap: the lot should not name these cases.
PricingStrategy
Pricing
Hourly, airport, staff, or weekend: hide the formula behind the pricing port.
NotificationChannel
Channel
SMS, email, push, or silent: the sender should not switch on channel type.
SettlementRule
Win / split
First-past, round-robin, or equal split: SettlementRule absorbs the follow-up.
Boolean flags versus a new class
The map names the ports. Here is the same parking follow-up in code: first as flags that reopen the lot, then as a that stays closed. A good seam is an interface with one job, injected at construction. That is with a : the lot does not name the new fee.
Closed for extension
isAirport, isEv, isStaff
Each flag multiplies branches. The next vehicle type touches park, exit, receipt, and the printer.
class ParkingLot {
Ticket park(Vehicle v) {
Spot spot;
if (v instanceof EvCar) {
spot = findEvSpot();
} else if (isAirport) {
spot = findNearestLarge();
} else {
spot = findNearest();
}
Money rate = v instanceof EvCar ? Money.rupees(20) : Money.rupees(10);
return Ticket.open(ids.next(), v, spot, clock.instant(), rate);
}
}Open for extension
The lot does not name the new fee
AirportPricing implements the pricing port. ParkingLot is not edited when the airport tariff arrives.
interface SpotAllocationStrategy {
Optional<Spot> allocate(Vehicle vehicle, List<Spot> free);
}
interface PricingStrategy {
Money fee(Ticket ticket, Instant exitAt);
}
class ParkingLot {
private final SpotAllocationStrategy allocation;
private final PricingStrategy pricing;
private final Clock clock;
private final IdGenerator ids;
ParkingLot(
SpotAllocationStrategy allocation,
PricingStrategy pricing,
Clock clock,
IdGenerator ids) {
this.allocation = allocation;
this.pricing = pricing;
this.clock = clock;
this.ids = ids;
}
Ticket park(Vehicle vehicle) {
Spot spot = allocation.allocate(vehicle, freeSpots())
.orElseThrow(LotFullException::new);
return Ticket.open(ids.next(), vehicle, spot, clock.instant());
}
Receipt exit(TicketId id) {
Ticket ticket = tickets.require(id);
Money fee = pricing.fee(ticket, clock.instant());
ticket.close(clock.instant());
return new Receipt(ticket.id(), fee);
}
}
class AirportPricing implements PricingStrategy {
public Money fee(Ticket ticket, Instant exitAt) {
Duration stay = Duration.between(ticket.enteredAt(), exitAt);
return AirportTariff.of(ticket.vehicle()).charge(stay);
}
}How to explain this in an interview
Say the follow-up out loud, then show the interface
You do not need the diagram in the room. You need a sentence that names the variation, then a signature that absorbs it.
- 1
Name the variation
"Pricing will change. Allocation will change. Vehicle identity will not." That sentence is the design.
- 2
Draw one port
Write PricingStrategy.fee(ticket, exitAt) before you write HourlyPricing. Keep time at the edge so fee math stays a pure function of the ticket and an Instant (a point in time).
- 3
Wire in Main
Production passes AirportPricing and Clock.systemUTC(). Tests pass HourlyPricing and a fixed Clock so time does not drift.
- 4
Refuse fake seams
IVehicle, ITicket, IFloor with one implementation is not extensibility. Abstract the algorithm, not the entity.
What still goes wrong after you add a strategy
The interview script above still fails if the name is there and the switch is not gone, or if you decorated nouns that will never vary. Three traps show up constantly.
Interface for every noun
IVehicle, ITicket, IFloor with one implementation is not extensibility (see the last interview step). Abstract the algorithm that will change, not the entity that will not.
Boolean flags for the follow-up
The isAirport / isEv / isStaff style from the closed-for-extension sample multiplies branches. A new class on an existing interface does not.
Strategy with the switch still inside
Naming a while fee() still switches on vehicle type is cargo-cult : the name without the structure. The cases must become classes.
Wrap-up
You have the story, the rule of thumb, and the traps. Check the three questions, then follow the related pages if a still feels abstract.
Check yourself
- A third pricing rule arrives. How many existing production classes must you edit besides Main?
- Where does Clock live: in PricingStrategy, or at the service edge that already read Instant?
- Which of allocation, Ticket, and Floor actually needs a seam on this problem?
Key takeaway
Predict the follow-up. Leave one interface per unstable algorithm. A new requirement should add a class, not reopen ParkingLot.