SwadhyAI Notes

All Low-Level Design notes

Low-level design

From a paragraph of requirements to a class list you can defend

Requirements-to-classes means turning a messy paragraph into use cases, entities, value objects, and verb owners — without dumping every method on ParkingLotManager. For example, “vehicles enter, get a ticket, leave, and pay by time and vehicle type” should become a short list of things with identity and a shorter list of actions that need homes.

We have a talking order and a tiny API. Now the owner hands you that paragraph. Do not start with a manager class. Start with nouns and verbs. This page is that translation.

Translation pipeline

A path from sentence to types, not a leap to a Manager

Your first class list is a guess. After you assign jobs, some nouns become values. Some verbs become strategies. ParkingLotManager should have disappeared. If it is still there with twenty methods, you stopped too early.

  1. 1

    Use cases

    Park, exit, pay — verbs you can say.

  2. 2

    Things + values

    What lasts vs what is just an amount.

  3. 3

    Who does each verb?

    Give the job to the object with the data.

  4. 4

    Small interfaces

    Chooser, pricing, store — then code.

Requirement → use cases → things and values → verb homes → who talks to whom → small interfaces → code

Underline the paragraph for nouns, values, and verbs

The parking problem says: vehicles enter, get a ticket, leave, and pay by time and vehicle type. Underline nouns that last over time. ParkingLot. Floor. ParkingSpot. Vehicle. Ticket. Those are things you can point at later. They usually become classes with an id — .

Underline values that should not have an id. Money. Duration. VehicleType. SpotType. Twenty rupees is not “that twenty.” Any other twenty of the same currency is the same value. Keep those small and hard to mix up. That is a .

Underline verbs: allocate, release, calculateFee, issueTicket. Ask which noun is responsible for each verb. Allocate needs a view of free spots. That lives near the lot, or near a small “how we pick a spot” type the lot uses. CalculateFee needs entry time, exit time, and a policy. That is not the spot’s job. IssueTicket is creation. The object that records tickets is a natural .

Worked underlining

Vehicles enter a parking lot with several floors of spots, get a ticket, leave, and pay by time and vehicle type. Two gates must never give out the same spot.

Sky: things with identity

Green: values

Amber: verbs that need a home

Not every noun becomes a class

“Customer name” may not need a type. Money and VehicleType do. is how fee bugs leak into every file. Skip nouns that are just labels. Keep nouns you will look up later. Promote numbers that have rules.

From the paragraphFirst cutWhy
Parking lot, floor, spotEntitiesYou point at them later; they have lifecycle.
Vehicle, ticketEntitiesPlate and ticket id survive field changes.
Vehicle type, spot typeEnums / valuesNo identity; they classify other things.
Money, duration, timeValue objectsRules (non-negative, same currency) belong here.
GateActor, not a class yetMay become a thread or a controller, not a domain noun.
Customer nameSkip or a fieldYou are not building a CRM today.
Pay / allocate / issueVerbs → methods or strategiesNeed a home, not a class named Pay.

A first class list, not a Manager

Write this on the board before you implement. It is allowed to be wrong. It is not allowed to be one class with every method. Domain modeling will refine who allocates. This list is the honest first cut.

java
// things you can point at later
class ParkingLot { }
class Floor { }
class ParkingSpot { }
class Vehicle { }
class Ticket { }

// values — no identity of their own
enum VehicleType { CAR, BIKE, EV }
enum SpotType { CAR, BIKE, EV }
final class Money {
  private final long paise;
  private final String currency;
  Money(long paise, String currency) {
    if (paise < 0) throw new IllegalArgumentException("paise");
    this.paise = paise;
    this.currency = Objects.requireNonNull(currency);
  }
  Money plus(Money other) { /* same currency, return new Money */ }
}

// verbs that need a home
interface SpotAllocation {
  Optional<ParkingSpot> choose(Vehicle vehicle);
}

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

interface TicketBook {
  Ticket issue(Vehicle vehicle, ParkingSpot spot, Instant at);
  Ticket require(TicketId id);
  void close(Ticket ticket);
}

Entity

Identity survives field changes

If I change the spot on this ticket, is it still the same ticket? Yes. Ticket T-1042 is still T-1042 if the car moves. Compare tickets by id, not by fee.

Put occupy on the spot because the spot is the for occupancy. A service that sets spot.free = false from the outside is how the invariant leaks.

Value

Replace, do not mutate

If I change the paise on this Money, is it still the same money? No. Throw the old one away. Values should be immutable so two threads can share a fee policy without surprise.

Read entity versus value object when this distinction starts leaking into equals().

Give each verb a home, then watch the Manager shrink

Nouns without jobs are empty boxes. Walk the verbs. If the only answer is still ParkingLot, you have not assigned yet. A thin ParkingLot that delegates is fine. A ParkingLot that prices, persists, and notifies is a God object again.

java
class ParkingSpot {
  private final SpotId id;
  private final SpotType type;
  private Vehicle occupant;

  boolean canFit(Vehicle vehicle) {
    return occupant == null && type.accepts(vehicle.type());
  }

  void occupy(Vehicle vehicle) {
    if (!canFit(vehicle)) throw new IllegalStateException("cannot occupy");
    occupant = vehicle;
  }

  void vacate() {
    occupant = null;
  }
}

class NearestEmptyAllocation implements SpotAllocation {
  private final List<Floor> floors;

  public Optional<ParkingSpot> choose(Vehicle vehicle) {
    return floors.stream()
        .flatMap(floor -> floor.spots().stream())
        .filter(spot -> spot.canFit(vehicle))
        .findFirst();
  }
}

class TicketBookInMemory implements TicketBook {
  private final Map<TicketId, Ticket> byId = new HashMap<>();

  public Ticket issue(Vehicle vehicle, ParkingSpot spot, Instant at) {
    Ticket ticket = Ticket.open(TicketId.newId(), vehicle, spot, at);
    byId.put(ticket.id(), ticket);
    return ticket;
  }
}

Allocate lives on a chooser that can see free spots. Occupy lives on the spot. Issue lives on the book that will look the ticket up later. CalculateFee will live on Pricing, which you already declared. The remaining ParkingLot (or ParkingService) only walks these objects. That walk is orchestration, not a God class.

The God object, written down so you can refuse it

ParkingLotManager.park, .price, .save, .notify is a script with a class name. Split the verbs until the Manager is thin or gone. If you catch yourself adding a twentieth method, stop and ask which noun should have received that method instead.

java
// refuse this shape
class ParkingLotManager {
  Ticket park(Vehicle v) { /* find, occupy, issue, sms, metrics */ }
  Money price(Ticket t) { /* switch on type, then on hour, then on coupon */ }
  void save(Ticket t) { /* JDBC */ }
  void notify(Ticket t) { /* email */ }
  void addFloor(Floor f) { }
  void reportDaily() { }
}

// prefer this shape: the lot is a walk, not a dump
class ParkingLot {
  Ticket park(Vehicle v) {
    ParkingSpot spot = allocation.choose(v).orElseThrow(LotFullException::new);
    spot.occupy(v);
    return tickets.issue(v, spot, clock.instant());
  }
}

After you assign, the list will change. That is the point.

Gate looked like a noun

In many rounds a gate is an actor: two threads calling park. You may not need a Gate class. You need to name the shared state (spots) and the one step that must not split (occupy). If the interviewer wants entry and exit lanes as objects, add them then — not from fear of missing a noun.

Pay looked like a class named PaymentService

Today pay is “compute a fee and return a receipt.” A payments company is out of scope. Pricing plus Receipt is enough. If they add coupons later, that is another policy type, not a new Manager method.

Every noun became a class, every number stayed a primitive

That is the two-sided mistake. Address is maybe a value. FloorNumber might be an int. Money must not be a double. Use the survival question: if I change this field, is it still the same thing?

You named Strategy before you named the owner

SpotAllocation is a seam because the interviewer hinted allocation will change. If they did not, a private method on the lot is enough. Do not invent six strategies for a lot with one rule. See protected variations.

Try the same method on a second paragraph

“People come in, take a token, wait, and pay by item and size.” Nouns that last: Order, Token, Item. Values: Size, Money. Verbs: takeOrder, bake, callNumber, price. Who prices? Not the token. Who issues the token? The object that will look it up when the number is called. A single God class that does every job should not survive that paragraph any more than ParkingLotManager survives the lot.

java
interface BakeryCounter {
  Token takeOrder(List<LineItem> items);
  Order collect(Token token);
}

final class LineItem {
  private final Item item;
  private final Size size;
  private final int qty;
}

interface Pricing {
  Money total(List<LineItem> items);
}

interface Kitchen {
  void bake(Order order);
  boolean ready(Token token);
}

Where to go once the list is honest

Takeaway

Underline nouns that last, values that should not have ids, and verbs that need a home. Ask who has the data. Ask who will look the thing up later. Let the Manager disappear. The first list is a guess; the assigned list is a design. Next we look at what interviewers actually mark besides “used a pattern,” and why silent perfect code still reads as weak design.