SwadhyAI Notes

All Low-Level Design notes

Three different nothings

Null, Optional, empty collections

Nullability design means each absence has one clear meaning — and you encode it in the type, not with a shared null. Example: find(ticketId) returns null when the ticket is gone. freeSpots() returns null when the lot is empty. The first null is “this id is unknown.” The second should be a list of size zero. Callers who write spots.size() crash on the empty lot. Mixing those meanings is how an shows up in an interview.

Missing one entity

. Return or a typed error. The caller must decide: show “not found,” or throw in a command that required the ticket.

Optional<Ticket> find(TicketId)

Empty collection

Zero parked cars, zero free spots, zero search hits. An . The loop is the branch. Never null.

List<ParkingSpot> freeSpots()

Do-nothing policy

A : NoDiscount, SilentNotifier. Behavior that is allowed to be absent without a branch on every call. See the Null Object pattern.

Money fee(..., Discount d)

Decision tree (say it out loud)

  1. Is this a collection the caller will iterate? Return a list. Empty is valid. Null is not.
  2. Is this one entity that might not exist? Optional for queries. Named exception for commands that required it (exit needs a ticket).
  3. Is this a strategy that might be “none”? Null Object, not Optional<Discount> on every fee line.
  4. Is this a programmer bug? requireNonNull in constructors. Do not let Ticket exist with a null id.

Java: three different nothings

java
class ParkingLot {
  Optional<Ticket> find(TicketId id) {
    return tickets.findById(id);          // missing one
  }

  List<ParkingSpot> freeSpots() {
    return List.copyOf(free);             // maybe empty, never null
  }

  Money fee(Ticket ticket, Discount discount) {
    return discount.apply(baseFee(ticket)); // NoDiscount, not null
  }
}

final class NoDiscount implements Discount {
  public Money apply(Money base) { return base; }
}

Be consistent in one API

If find returns Optional, do not also return null from findByPlate. Pick Optional for lookups, exceptions for illegal commands, empty lists for searches. That policy is part of class API design and error handling. SQL NULL in a column is a storage concern; map it at the repository. The domain Ticket should not have three nullable strings “because the table does.”

Null collections

freeSpots() returning null means every for-loop needs a guard. Return empty. The caller’s loop just runs zero times. This is also a collections ownership rule: copy, do not share, do not null.

Optional wrapping a collection

Optional<List<Ticket>> has two empties. Callers will get() and then null-check. Use List. Use Optional only for a single missing value.

Java: command vs lookup

java
class ParkingLot {
  // Query — absence is a value
  Optional<Ticket> find(TicketId id) {
    return tickets.findById(id);
  }

  // Command — absence is a failure of the use case
  Receipt exit(TicketId id) {
    Ticket ticket = tickets.findById(id).orElseThrow(() -> new UnknownTicket(id));
    return ticket.close(clock.instant(), pricing);
  }
}

Optional is not a field strategy

Putting Optional<Instant> exitTime on Ticket and forgetting to unwrap it is how NPEs move into the entity. Prefer a state machine: OPEN tickets have no exit; CLOSED tickets have an Instant. Illegal CLOSED→ACTIVE is a typed error, not a null exit time. See state vs status.

HLD cousin: nullable columns

Tables use NULL for unknown columns. That is not permission for Ticket to be a bag of nulls. Map at the repository: a NULL exit_ms becomes Optional.empty() or a CLOSED vs OPEN type. Schema evolution lives in data modeling and schema evolution; the LLD rule is still “empty list vs missing one vs no-op policy.”

requireNonNull is not Optional

Constructors use Objects.requireNonNull because a Ticket with a null id is a bug, not a lookup miss. Optional is for answers that may be absent. requireNonNull is for values that must exist for the object to be well-formed. Mixing them — Optional<TicketId> id on Ticket — makes every method unwrap an identity that should never have been missing.

An untyped process(Object) method returns Object, which is often null for “full,” “missing,” and “empty list.” That is three meanings in one return. park(Vehicle) returning Ticket, find returning Optional, freeSpots returning List is how you stop encoding nothing as null. The class API and nullability are the same design viewed from names and from absence.

Java: never Optional of a list

java
// Wrong — two empties
Optional<List<Ticket>> openMaybe();

// Right — one empty
List<Ticket> openTickets();

class ParkingLot {
  List<Ticket> openTickets() {
    return List.copyOf(tickets.findOpen()); // empty list if none
  }
}

Query miss

The attendant looks up a plate. Empty Optional is a valid answer. The UI shows “not in the lot.”

Command miss

The driver presents a ticket that was never issued. exit cannot complete. UnknownTicket is the API. Do not return a null Receipt.

Exercises

  1. A teammate returns Optional.empty() from freeSpots when the lot is full. Rewrite the signature and the one-line reason.
  2. exit(TicketId) when the ticket is missing: Optional or UnknownTicket? Why is this a command, not a query?
  3. Discount may be “none.” Why is NoDiscount better than passing null into fee()?
Null ObjectErrorsCollection APIs

Worked scene: the attendant’s loop

for (ParkingSpot s : lot.freeSpots()) draw(s). If freeSpots returns null when the lot is packed, the loop never starts — it NPEs. If it returns empty, the canvas is blank, which is correct. find(id) in the same UI uses Optional: no ticket card, not a crash. fee(..., NoDiscount) never branches. Three widgets, three nothings, zero shared null.

An untyped process(Object) method that returns null cannot tell the UI which widget to blank. Typed absences can. That is API design viewed from the empty case.

How to explain this in an interview

Missing one ticket

Optional on find. UnknownTicket on exit. Not a null Receipt.

Zero free spots

Empty list from freeSpots(). Packed lot is not a missing list.

No discount

NoDiscount object. fee() does not null-check.

Null id in constructor

requireNonNull. That is a bug, not Optional<TicketId>.

In about a minute, say: “find returns Optional. freeSpots returns an empty list, never null. NoDiscount is a real object. I will not wrap a list in Optional. An untyped process(Object) method returning null meant three things; these types mean one each.”

Commands that required the ticket throw UnknownTicket. Lookups stay Optional. Packed lots are empty collections, not missing collections.

Java: empty list vs missing ticket

java
List<ParkingSpot> freeSpots() {
  return occupied == capacity ? List.of() : List.copyOf(free);
}

Optional<Ticket> find(TicketId id) {
  return tickets.findById(id);
}

Takeaway

A missing ticket is not the same as zero parked cars, and neither is a do-nothing discount. Optional for one absent entity, empty lists for collections, Null Object for policies. Never return null for something the caller will iterate.