SwadhyAI Notes

All Low-Level Design notes

Named failures

Errors and domain exceptions

Error handling in LLD means naming the failure so callers can catch, log, and show the right message. Example: the lot is full and park throws RuntimeException("fail"). The gate app cannot tell full from a . SpotUnavailable is a name you can catch. InvalidTransition is how CLOSED tickets refuse to reopen.

Exception catalog

A is part of the API. Write the names on the whiteboard next to park and exit. HTTP status mapping is an edge concern — see REST API design. The lot throws types, not status codes.

TypeWhenKind
SpotUnavailablepark when no fitting free spotcommand expected failure
UnknownTicketexit/pay/find-required with a bad idcommand expected failure
InvalidTransitionclose() from already CLOSED, or CLOSED→ACTIVEillegal operation
LotClosedpark after hours if the story has hourspolicy failure
IllegalArgumentExceptionblank plate at the factory — input, not domain plotboundary validation

Java: named failures

java
class SpotUnavailable extends RuntimeException {
  SpotUnavailable() { super("no fitting free spot"); }
}

class UnknownTicket extends RuntimeException {
  UnknownTicket(TicketId id) { super(id.toString()); }
}

class InvalidTransition extends RuntimeException {
  InvalidTransition(TicketStatus from, TicketStatus to) {
    super(from + " -> " + to);
  }
}

class ParkingLot {
  Ticket park(Vehicle vehicle) {
    return allocation.choose(freeSpots(), vehicle)
        .orElseThrow(SpotUnavailable::new);
  }

  Receipt exit(TicketId id) {
    Ticket ticket = tickets.findById(id).orElseThrow(() -> new UnknownTicket(id));
    return ticket.close(clock.instant(), pricing);
  }
}

User can cause it

Full lot, bad id, pay twice with a different amount. Named domain error. Not a bug.

You thought it impossible

Missed enum branch, null id in a constructor. Assertion-style failure. Fix the code.

Lookup miss

Queries return Optional. Commands that required the entity throw UnknownTicket. See nullability.

Throw vs Result vs null

Commands throw named errors or return a . Do not swallow them. Do not replace them with null — park() returning null when full looks like a missing ticket. Pick one, then keep it when the API evolves.

Checked vs unchecked in an interview

Unchecked domain exceptions keep signatures readable. If the team’s style is checked, a ParkingException hierarchy is fine. What interviewers punish is throw new RuntimeException("error") with a string the caller must parse. The type is the contract.

Make illegal states hard, then throw the rest

You cannot construct a Ticket with a null id. You cannot call close() from CREATED if the type only exposes activate() then close(). Remaining throw InvalidTransition. Pair with state transitions.

SQLException is not a domain error

Catch it in the repository adapter. Translate to a persistence failure the application service understands — or let it bubble as infrastructure. ParkingLot must not declare throws SQLException. That would put JDBC on the lot.

Java: Ticket guards the transition

java
class Ticket {
  private TicketStatus status;

  Receipt close(Instant exit, PricingStrategy pricing) {
    if (status != TicketStatus.ACTIVE) {
      throw new InvalidTransition(status, TicketStatus.CLOSED);
    }
    status = TicketStatus.CLOSED;
    return new Receipt(id, pricing.fee(this, exit));
  }
}

Mistakes

  • throw new RuntimeException("error") — the message is not a contract. Callers will parse strings. Give the failure a type.
  • Returning null on failure — park() returning null when full looks like a missing ticket. Throw or return a Result. Do not reuse null for three meanings.
  • Catch and ignore — empty catch in exit “to keep the gate open” hides UnknownTicket. Fail loud; let the controller decide the message.

HTTP status is not the domain type

The controller may map SpotUnavailable to 409 and UnknownTicket to 404. ParkingLot still throws types. If park throws 409 as an int, the lot has learned HTTP. Keep the catalog on the domain; keep the status table on the edge. That is the same boundary as ParkRequest: the outside world has codes, the inside has names.

An untyped process(Object) method that returns a bag of ok/false and a string msg is an untyped error channel. Callers will switch on msg. Named exceptions (or a Result with a typed failure) are the class-API version of problem+json without dragging HTTP into the lot.

Java: Result if you refuse to throw on full

java
sealed interface ParkResult {
  record Parked(Ticket ticket) implements ParkResult {}
  record Full() implements ParkResult {}
}

class ParkingLot {
  ParkResult tryPark(Vehicle vehicle) {
    return allocation.choose(freeSpots(), vehicle)
        .map(spot -> new ParkResult.Parked(openTicket(vehicle, spot)))
        .orElseGet(ParkResult.Full::new);
  }
}

Do not swallow at the repository

Catching SQLException and returning null from findById turns infrastructure failure into “ticket missing.” The attendant refunds a car that is still in the lot. Translate or bubble; do not pretend the row was absent. UnknownTicket is for a successful query that found nothing. A down database is not UnknownTicket.

Exercises

  1. List three named exceptions for a parking lot. Which ones are thrown by park vs Ticket.close?
  2. Should find(TicketId) throw UnknownTicket or return Optional? Why is that a different kind of miss than exit?
  3. A repository throws SQLException. Where do you catch it, and what does ParkingLot’s signature look like?
ValidationState transitionsClass API

Worked scene: full lot at the gate

park throws SpotUnavailable. The controller maps it to a “lot full” sign. A missed enum in allocation throws a different exception — that one pages an engineer. If both were RuntimeException("fail"), the sign and the pager look the same. Named types are how the edge decides. ParkingLot still has no HTTP status and no JDBC.

CLOSED→ACTIVE is InvalidTransition on Ticket, not a null status. Make the call hard to represent; throw when a caller still tries. Queries keep Optional. Commands that required the ticket throw UnknownTicket.

How to explain this in an interview

Lot full

SpotUnavailable (or ParkResult.Full). Never null Ticket. Never RuntimeException("fail").

Unknown id on find

Optional.empty(). The query succeeded; the row was missing.

Unknown id on exit

UnknownTicket. The command could not run.

SQLException

Adapter only. ParkingLot signatures stay clean of JDBC.

In about a minute, say: “SpotUnavailable when full, UnknownTicket when exit misses, InvalidTransition on CLOSED→ACTIVE, Optional on find. SQLException stays in the adapter. park still returns Ticket, not null.”

If they ask for Result instead of throw, pick one style and keep it when the API grows. Mixing null, Result, and exceptions for the same full-lot case is the smell process(Object) had with a string msg field.

An untyped process(Object) method returning ok/false is not a catalog. Named types are.

Takeaway

If the user can cause it, it is a named domain error — not RuntimeException("fail"). Queries use Optional. Commands that need the entity throw. SQLException never appears on ParkingLot. Make illegal states hard to represent; shout about the rest.