SwadhyAI Notes

All Low-Level Design notes

Low-level design

What low-level design is actually asking

Low-level design asks how objects inside one program should share the work: which types exist, which methods they expose, and which object owns each responsibility. For example, a parking lot that starts as one file with a few arrays and if-statements can park a car — until you must add EV spots, two fee rules, and two gates that must never assign the same spot.

At that point the file may still “work,” but it is no longer a design. New requirements edit the same class in five places. LLD is the skill of splitting responsibilities so change lands in the right type instead of rewriting one god class.

What this round grades

1

Name the use cases

Park, exit, pay — start with verbs you can say out loud.

2

Name things with identity

Ticket, spot, vehicle — things you can point at later.

3

Give each verb a home

If every answer is “the Manager,” keep splitting.

4

Leave a gap for change

Pricing and allocation almost always change next.

The work is splitting responsibilities, not typing syntax

is deciding how objects share work inside one program. You choose which concepts become classes, which actions become methods, and who may mutate which state. A new rule should usually land as a new type, not as one more branch inside the same giant class.

It is not “can you write Java.” Classes and interfaces live in the language track. It is not system design: you are not placing Kafka, Redis, or a load balancer. It is not DSA: you may use a HashMap, but the grade is still the object model, not a clever Big-O trick.

Think of three interview units. DSA asks: how do I compute this efficiently? LLD asks: how should the objects share the work? System design asks: how should many machines share the work? This subject is the middle one. Keep those three talks apart, or you will answer the wrong interview.

Start with verbs you can say out loud: park, exit, pay. Those are . Then name the things you can point at later: ticket, spot, vehicle. Then ask which object performs each verb. If the answer is “the Manager,” keep asking. A is how every leftover responsibility collapses back into one class with a confident-looking name.

Three interviews, three units of work

DSA, LLD, and system design answer different questions

The same product name can show up in all three rounds. The unit of work is what changes. If you bring the wrong unit, you can sound fluent and still miss what the interviewer is grading.

DSA

How do I compute it efficiently?

Find a free index. Sort fees. Walk a graph of floors. The grade is correctness and complexity, not which object owns the ticket.

LLD

How should objects share the work?

Who allocates? Who prices? Who stores? Who must not know about SQL? This page lives here.

System design

How should many machines share the work?

Gates in two cities, a cache of free spots, a queue for receipts. That is a different note.

The next page, HLD versus LLD, compares distributed design (services, stores, failure domains) with in-process design (classes, methods, threads). Read it when you feel Redis creeping into a class diagram.

The public shape is tiny on purpose

Callers do not need twenty methods. They need a they can state in one breath. Everything else is how the object model keeps that contract honest when EV pricing or a second allocation rule arrives tomorrow.

java
interface ParkingLotService {
  Ticket park(Vehicle vehicle);
  Receipt exit(TicketId ticketId);
}

That is the whole public surface. Park returns a ticket you can hold. Exit consumes that ticket and returns a receipt. If you cannot write those two signatures in the first five minutes, you are still guessing at the problem. The approach page is about getting this contract agreed before you type the rest.

God-object file

ParkingLotManager does every job

This compiles and parks a car. It is still a weak design. Pricing, storage, and notifications all sit in the same class, so the next requirement edits all of them at once.

java
class ParkingLotManager {
  List<Spot> spots = new ArrayList<>();
  Map<String, Ticket> tickets = new HashMap<>();

  Ticket park(Vehicle v) {
    for (Spot s : spots) {
      if (s.free) {
        s.free = false;
        s.plate = v.plate;
        Ticket t = new Ticket(UUID.randomUUID().toString(), v, s, now());
        tickets.put(t.id, t);
        sms.notify("parked " + v.plate);
        return t;
      }
    }
    throw new RuntimeException("full");
  }

  Receipt exit(String id) {
    Ticket t = tickets.get(id);
    long hours = (now() - t.in) / 3_600_000;
    double fee = t.vehicle.type.equals("EV") ? hours * 30 : hours * 20;
    t.spot.free = true;
    return new Receipt(id, fee);
  }
}

Split responsibilities

Each verb has a home

The lot still orchestrates. It does not invent fees, walk every spot itself, or send SMS. Occupancy lives on the spot so the cannot leak into five files.

java
class ParkingLot implements ParkingLotService {
  private final SpotChooser chooser;
  private final TicketBook tickets;
  private final Pricing pricing;
  private final Clock clock;

  public Ticket park(Vehicle vehicle) {
    ParkingSpot spot = chooser.choose(vehicle)
        .orElseThrow(LotFullException::new);
    spot.occupy(vehicle);
    return tickets.issue(vehicle, spot, clock.instant());
  }

  public Receipt exit(TicketId ticketId) {
    Ticket ticket = tickets.require(ticketId);
    Money fee = pricing.fee(ticket, clock.instant());
    ticket.spot().vacate();
    tickets.close(ticket);
    return new Receipt(ticket.id(), fee);
  }
}

class ParkingSpot {
  private Vehicle occupant;

  void occupy(Vehicle vehicle) {
    if (occupant != null) {
      throw new IllegalStateException("spot already held");
    }
    occupant = vehicle;
  }

  void vacate() {
    occupant = null;
  }
}

Leave a gap

Pricing and allocation will change. Plan for that without naming a pattern yet.

Interviewers almost always add a second vehicle type or a second fee rule. If those decisions live in a switch inside exit, the lot gets edited in five places. If they live behind small types, you add a class and leave the lot unchanged.

A is not “I will use Strategy.” A seam is a named job that can vary: choose a free spot, compute a fee. You may later plug a strategy into that job. First you must know who calls it. Pattern names without owners are decoration.

java
interface SpotChooser {
  Optional<ParkingSpot> choose(Vehicle vehicle);
}

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

// Duration.toHours() truncates; bill by ceiling from seconds (started hour).
static long billableHours(Duration stay) {
  long seconds = stay.getSeconds();
  if (seconds < 0) throw new IllegalArgumentException("negative stay");
  return Math.max(1, seconds / 3600 + (seconds % 3600 == 0 ? 0 : 1));
}

class HourlyByVehicleType implements Pricing {
  public Money fee(Ticket ticket, Instant exitTime) {
    Duration stay = Duration.between(ticket.enteredAt(), exitTime);
    long hours = billableHours(stay);
    long paise = hours * ticket.vehicle().type().hourlyPaise();
    return Money.inr(paise);
  }
}

Duration.toHours() truncates toward zero — 1h59m becomes 1. Hourly parking that bills a started hour must ceil from seconds (or nanos), then apply the one-hour minimum.

A working order you can say out loud

You do not need a perfect class diagram before you talk. You need a sequence that keeps responsibilities from collapsing back into one manager class.

  1. 1

    Name the use cases

    Park, exit, pay. If two gates exist, say that now. If payments are out of scope, say that too.

  2. 2

    Name the things with identity

    Ticket, Spot, Vehicle, Floor. Things you can point at later usually become entities.

  3. 3

    Name the values

    Money, VehicleType, Duration. Twenty rupees is not “that twenty.” Keep them small and hard to mix up.

  4. 4

    Give each verb a home

    Who allocates? Who prices? Who stores? Who protects occupancy? If the answer is still “the Manager,” keep going.

  5. 5

    Protect one invariant

    A spot is held by at most one vehicle. Put that rule on the spot, not in a comment on the service.

  6. 6

    Leave the seam

    Chooser and pricing should be replaceable. You do not have to implement three variants today.

Walk one happy path before you draw twenty boxes

Candidates freeze when they try to recall “the” class diagram. Walk the car instead. A vehicle arrives. Someone chooses a spot. The spot becomes occupied. A ticket is issued. Later the ticket is loaded, a fee is computed, and the spot is freed. If you can narrate that chain, the classes fall out of the verbs.

Vehicle arrives
Chooser picks a spot
Spot.occupy
TicketBook.issue
Later: price + vacate
java
void happyPath() {
  ParkingLot lot = new ParkingLot(
      List.of(floorWith(2, SpotType.CAR)),
      new NearestEmptyChooser(),
      new InMemoryTicketBook(),
      new HourlyByVehicleType(),
      Clock.systemUTC()
  );

  Ticket ticket = lot.park(new Car("KA-01-AB-1234"));
  assert ticket.spot().isOccupied();
  assert ticket.vehicle().plate().equals("KA-01-AB-1234");

  Receipt receipt = lot.exit(ticket.id());
  assert receipt.fee().paise() > 0;
  assert ticket.spot().isFree();
}

Even a crude test like this keeps you honest when you split the Manager apart. If occupy and vacate are not on the spot, the test can still pass while two gates hand out the same stall. That race belongs in race conditions, but you should name the shared state today.

Mistakes that look like design

Starting with a pattern name

“I will use Strategy” is not a design. First decide who owns allocation. Strategy is how you vary that choice later. Opening with a catalog of patterns wastes the first ten minutes and hides the missing owner.

Building a ParkingLotManager

One class that parks, prices, saves, and notifies is a God object. Interviewers see it in a glance. Split the jobs until the remaining service is a thin walk of other objects.

Optimizing a HashMap before the model exists

A HashMap of tickets is fine. A clever O(1) free-spot index is optional. The grade is still who may mutate occupancy, not whether you beat a linear scan on twenty stalls.

Dropping Redis into the class list

Unless they asked for high-level design, a cache is a class with get and put. Naming a product is not a design of objects. See HLD versus LLD.

How this note connects to the rest of the track

This page is the door. The next four fundamentals notes turn the same parking lot into a method, a class list, and a scorecard. Domain modeling then asks who allocates. SOLID and GRASP give names to jobs you already placed. Patterns come last, as tools, not as the starting point.

Try this before you move on

  1. Write only the ParkingLotService interface and the names of four collaborators. Do not implement yet. Can you defend each name?
  2. Add EV spots as a follow-up. Which files change if pricing lives in exit? Which files change if pricing is its own type?
  3. Two gates call park at once. Where is the shared state? What must not split? You do not need a lock in minute five. You do need the sentence.

Takeaway

Low-level design is splitting work among objects so a new rule does not rewrite one giant class. Keep the public API tiny. Put each verb in a home. Protect the one rule that must stay true. Leave a gap where change always arrives. The next page shows the same parking lot at two altitudes — distributed HLD versus in-process LLD — and why mixing those two jobs is how strong candidates still fail the round.