Courses › Low-Level Design › Foundations
Handling Extension Follow-Ups
Classifying "now add X" changes, where each one plugs in, and refactoring calmly when it doesn't.
Almost every LLD round has a second half: once your core design works, the interviewer changes the requirements. "Now add electric-vehicle charging spots." "Now notify users when their order ships." "Now make it thread-safe." These extension follow-ups are where interviewers find out whether your design was actually extensible, or only looked that way.
Why interviewers ask them
A design that handles the original prompt tells them little. Real software changes constantly, so they want to see:
- whether the change fits into an existing seam or forces a rewrite;
- whether you can say where the change goes before typing;
- whether you notice when the design is missing an abstraction, and refactor calmly.
A four-step response
- Restate and clarify. "So some spots have chargers, and charging is billed separately? Can a non-electric car park in a charging spot?"
- Classify the change. Most follow-ups fall into a handful of kinds (table below), and each kind has a usual home in the design.
- Say the plan out loud: which classes change, which are new, and which stay untouched. "Charging is a new spot feature plus a new pricing rule.
ParkingLotandTicketdon't change." - Make the change and re-run your tests or demo. If the change touched more than expected, say why and what abstraction would have prevented it.
The common kinds of follow-up
A worked example: pricing a parking lot
Suppose your first version computed the fee inside Ticket:
class Ticket:
def __init__(self, vehicle_type, entry_hour):
self.vehicle_type, self.entry_hour = vehicle_type, entry_hour
def fee(self, exit_hour):
hours = max(1, exit_hour - self.entry_hour)
rate = 5 if self.vehicle_type == "truck" else 2
return hours * rateThe interviewer asks for weekend rates and an electric-vehicle charging surcharge. Adding more if branches here would mix every pricing rule into Ticket. Instead, say: "Pricing varies independently of tickets, so I'll move it behind a strategy, and make the surcharges composable."
from abc import ABC, abstractmethod
class PricingRule(ABC):
@abstractmethod
def price(self, ticket, exit_hour, base): ...
class HourlyRate(PricingRule):
RATES = {"car": 2, "truck": 5, "ev": 2}
def price(self, ticket, exit_hour, base):
hours = max(1, exit_hour - ticket.entry_hour)
return base + hours * self.RATES[ticket.vehicle_type]
class WeekendMultiplier(PricingRule):
def __init__(self, factor):
self.factor = factor
def price(self, ticket, exit_hour, base):
return base * self.factor if ticket.weekend else base
class ChargingSurcharge(PricingRule):
def __init__(self, per_hour):
self.per_hour = per_hour
def price(self, ticket, exit_hour, base):
if not ticket.charging:
return base
return base + (exit_hour - ticket.entry_hour) * self.per_hour
class Ticket:
def __init__(self, vehicle_type, entry_hour, weekend=False, charging=False):
self.vehicle_type, self.entry_hour = vehicle_type, entry_hour
self.weekend, self.charging = weekend, charging
class PricingPolicy:
def __init__(self, rules):
self.rules = rules # applied in order
def fee(self, ticket, exit_hour):
total = 0
for rule in self.rules:
total = rule.price(ticket, exit_hour, total)
return total
policy = PricingPolicy([HourlyRate(), WeekendMultiplier(1.5), ChargingSurcharge(3)])
print(policy.fee(Ticket("car", 9), 12)) # 6
print(policy.fee(Ticket("car", 9, weekend=True), 12)) # 9.0
print(policy.fee(Ticket("ev", 9, weekend=True, charging=True), 12)) # 18.0What to say while doing this:
- "
Ticketis now plain data, andPricingPolicydoesn't know any specific rule." - "Order matters: the weekend multiplier applies to the base rate but not to charging. I made that explicit by putting it before the surcharge. I'd confirm that with you."
- "A new rule, say a loyalty discount, is one new class and one line of configuration."
This is the same move as in the Discount Engine walkthrough, at a smaller scale.
Designing for follow-ups you can't predict
You can't know which extension is coming, and building every abstraction up front is over-engineering. Use these defaults instead:
- Inject dependencies such as the clock, strategies, the storage and notification senders through the constructor. Every follow-up about time, persistence or notifications then becomes easy.
- Keep the main flow in one place (for example,
ParkingLot.parkandParkingLot.leave) so you have an obvious spot to raise events or add checks. - Use enums and small value objects rather than raw strings and tuples, so adding a field doesn't break callers.
- Put each rule in one place. If the same condition appears in three methods, a follow-up will make you change all three.
[!INTERVIEW] Refactoring is not a failure If the follow-up doesn't fit, say so plainly: "This doesn't fit my design, because pricing lives in Ticket. I'll pull it out into a strategy first, which takes about two minutes, then add the rule." Interviewers rate that well. Forcing the change in with a pile of if statements, or pretending it fits, rates badly.
Thread-safety follow-ups
"Now make it thread-safe" is the single most common follow-up. A good answer:
- Find the shared, changing state: the spot map, seat status, account balances.
- Find the operations that must be atomic: "check that the spot is free, then assign it".
- Choose the lock granularity: one lock for the whole object is simple and correct; per-item locks scale better but need a consistent lock order to avoid deadlocks.
- Keep slow work outside the lock, such as sending notifications or calling a payment provider.
See Concurrency & Thread Safety and the Movie Ticket Booking walkthrough.
[!TIP] Practice Parking Lot · Car Rental System · Notification Service · Logging Framework