SwadhyAI Notes

All Low-Level Design notes

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.

Diagram text: the exact Mermaid definition below preserves the nodes, labels, and connections. Enable JavaScript to view the rendered diagram.
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.

java
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.

java
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. 1

    Name the variation

    "Pricing will change. Allocation will change. Vehicle identity will not." That sentence is the design.

  2. 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. 3

    Wire in Main

    Production passes AirportPricing and Clock.systemUTC(). Tests pass HourlyPricing and a fixed Clock so time does not drift.

  4. 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

  1. A third pricing rule arrives. How many existing production classes must you edit besides Main?
  2. Where does Clock live: in PricingStrategy, or at the service edge that already read Instant?
  3. 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.