SwadhyAI Notes

All Low-Level Design notes

Machine-coding case study

Parking lot

A parking lot issues a ticket on park and a receipt on exit. Allocation (which free spot fits this vehicle) and pricing (how much the stay costs) are strategies. Vehicle types come from a factory. Occupy must be atomic so two gates cannot take the same spot. The one-file program that parks a car becomes a knot once EV (electric vehicle) spots and airport fees arrive.

The real question is not "can I store spots in a list?" It is: can two gates take the same spot, and can I add a pricing rule without editing ParkingLot?

Interview path

01

Name the nouns

Name lot, floor, spot, vehicle, and ticket. Money and Duration are values, not entities.

02

Put decisions on seams

Put allocation and pricing on strategy seams. Create vehicle types through a factory.

03

Protect occupy

Choose-and-mark must be one atomic step. Checking isFree then occupy is a race.

What actually happens when requirements keep coming

After the first park/exit demo, the next requirement is almost always a new vehicle type or a new fee. If ParkingLot contains if (type == EV) and if (airport), every follow-up edits the lot. That is the smell you are designing against.

Allocation is a decision that needs free spots that fit the vehicle. is a decision that needs time and a policy. Ticket creation needs an id generator and a Clock. If ParkingLot does all three with switches, the follow-up edits the lot. If each is a strategy (a swappable decision object) or factory (creates typed objects without switch soup), the lot stays boring, which is the point.

EV spots arrive

New spot type plus a filter in the allocation strategy. ParkingService.park does not grow a boolean.

Airport vs mall fees

Swap the PricingStrategy at construction. Tests freeze Clock so a two-hour stay is deterministic.

Class and responsibility walk

Once the story is clear, name the nouns that keep identity. Money and Duration do not. The public API stays tiny: park returns a ticket; exit returns a receipt. Everything else is a collaborator. See domain modeling and information expert.

TypeResponsibility
ParkingLotOwns floors and the public surface. Does not pick a pricing formula.
FloorA slice of spots. Answers which spots are free on this level.
ParkingSpotOne rectangle of concrete. Protects the one-vehicle invariant.
VehicleIdentity of what arrived. Type decides which spots fit.
TicketProof of a stay. Issued at park, closed at exit.
ParkingServiceThin controller for park and exit. Collaborators own the decisions.
Factory for vehicle (or spot) types so a new type is a class, not a new enum branch in park().
Strategy for nearest-spot vs first-fit, and for hourly vs airport fees.

Park path and Java API sketch

With nouns named, walk the happy path. Gate A does not rummage through floors. It asks ParkingService. The service asks the allocation strategy, then asks the spot to occupy itself. Ticket minting happens only after occupy succeeds.

Diagram text: the exact Mermaid definition below preserves the nodes, labels, and connections. Enable JavaScript to view the rendered diagram.
Read the diagram definition
sequenceDiagram
    autonumber
    participant Gate as Gate A
    participant Lot as ParkingService
    participant Alloc as AllocationStrategy
    participant Spot as ParkingSpot
    participant Clock as Clock

    Gate->>Lot: park(vehicle)
    Lot->>Alloc: choose(free spots, vehicle)
    Alloc-->>Lot: Optional spot 101
    Lot->>Spot: tryOccupy()
    alt occupied atomically
        Lot->>Clock: now()
        Lot-->>Gate: Ticket
    else already taken
        Lot-->>Gate: retry or LotFull
    end

Seams in code

The sketch below is the seams, not a finished product. ParkingSpot owns the compare-and-set (CAS: one atomic check-and-update). ParkingService does not set a boolean on a HashMap of spots.

java
interface SpotAllocationStrategy {
  Optional<ParkingSpot> choose(List<ParkingSpot> free, Vehicle vehicle);
}

interface PricingStrategy {
  Money fee(Ticket ticket, Instant exitTime);
}

interface VehicleFactory {
  Vehicle from(VehicleType type, String plate);
}

class ParkingService {
  private final SpotAllocationStrategy allocation;
  private final PricingStrategy pricing;
  private final Clock clock;

  Ticket park(Vehicle vehicle) { /* choose, occupy, issue */ }
  Receipt exit(TicketId id) { /* fee, free spot, close ticket */ }
}

class ParkingSpot {
  private final AtomicReference<SpotStatus> status =
      new AtomicReference<>(SpotStatus.FREE);

  boolean tryOccupy() {
    return status.compareAndSet(SpotStatus.FREE, SpotStatus.OCCUPIED);
  }

  boolean vacate() {
    return status.compareAndSet(SpotStatus.OCCUPIED, SpotStatus.FREE);
  }
}

Concurrency: two gates, one spot

The sketch assumes occupy is safe. The invariant is one vehicle per spot. Gate A and Gate B both seeing spot 101 free is a check-then-act race. Occupy must be one step: compare-and-set, or a lock around choose-and-mark. Say that story before you type synchronized. Same lesson as thread safety and race conditions.

Diagram text: the exact Mermaid definition below preserves the nodes, labels, and connections. Enable JavaScript to view the rendered diagram.
Read the diagram definition
sequenceDiagram
    participant A as Gate A
    participant S as Spot 101
    participant B as Gate B

    A->>S: isFree()? true
    B->>S: isFree()? true
    A->>S: occupy()
    B->>S: occupy()
    Note over S: Two cars, one rectangle of concrete

After a failed tryOccupy, allocation may retry with remaining free spots. Do not mark 101 occupied in the strategy and again in the spot: the spot is the lock.

Invalid cases and what the follow-up changes

Atomic occupy is necessary but not enough. These cases must fail cleanly, and the usual interview extensions should land as new classes, not new booleans on ParkingLot.

Invalid cases the design must reject

  • Park a motorcycle into a taken compact spot

    tryOccupy fails. Do not overwrite the occupant or mint a second ticket for 101.

  • Exit with an unknown ticket id

    No receipt. The lot does not invent a stay. Return a domain error, not a null fee.

  • Exit a ticket twice

    Second exit is illegal. Ticket is CLOSED. Idempotent already-closed is a follow-up.

  • EV that needs a charger, only compact free

    Allocation returns empty. Do not downcast the vehicle into a non-charging spot.

  • Negative duration because Clock went backwards

    Injected Clock should be monotonic in tests. Pricing must not emit negative Money.

What the follow-up changes

Tests freeze the clock so a two-hour stay is deterministic. The extension they always add (a new vehicle type or a new fee rule) should land as a new class, not a new boolean on ParkingLot.

  • Handicap or compact-only floors

    Filter in allocation. Floor still only lists spots. Do not add Floor.handicapMode.

  • First hour free, then hourly

    New PricingStrategy. Ticket still only stores entry Instant. Fee math stays off the ticket.

  • Valet that parks anywhere

    Different allocation strategy that ignores nearest-to-elevator. Same park() signature.

  • Receipt printing / SMS

    Side effect after exit. Not a method on ParkingSpot. A listener or thin adapter outside the domain.

Wrap-up

You have the nouns, the seams, the race, and the rejects. After EV and airport pricing, a one-file program is a knot. Put allocation and pricing on strategy seams. Create vehicle types through a factory. Occupy is compare-and-set. Clock is injected. ParkingLot stays boring on purpose.

Key checks

  • · Retry allocation after a failed tryOccupy so another free spot can still win.
  • · Negative duration must not emit negative Money; keep Clock monotonic in tests.
  • · Receipt SMS is a side effect after exit, not ParkingSpot.print().
  • · First hour free is a new PricingStrategy, not a field on Ticket.

You're done when

Two gates cannot both occupy the same spot (tryOccupy / CAS wins for one). Park returns a ticket only after occupy succeeds. Exit returns a receipt. A new fee rule or vehicle type lands as a strategy or factory class, not a new if inside ParkingLot.