SwadhyAI Notes

All Low-Level Design notes

Behavioral modeling

State machines

A state machine is a finite set of statuses plus the allowed transitions between them. You draw the legal arrows first; illegal arrows are bugs waiting to happen.

Example: a ticket is created, becomes active when the car parks, paid when the driver pays, and closed when the gate opens. A webhook retries and tries to make it ACTIVE again. Two booleans will not show that bug — a transition table will.

Ticket machine

CREATED → ACTIVE → PAID → CLOSED

Four nodes. Three legal arrows. Everything else throws. This picture is the spec. Coding style comes later.

CREATED

Issued, car not yet counted as parked

ACTIVE

Stall occupied, clock running

PAID

Fee taken, gate may open

CLOSED

Terminal. Record only.

CLOSED → ACTIVE is illegal

Also illegal: PAID → ACTIVE, CLOSED → PAID, CREATED → CLOSED. A retry does not drive backwards on the happy path.

Ticket

  CREATED
     |  activate()
     v
  ACTIVE
     |  pay()
     v
  PAID
     |  close()
     v
  CLOSED

CLOSED → ACTIVE   illegal
PAID → ACTIVE     illegal
CLOSED → PAID     illegal

Commands, not setStatus

Keep the current state as a . Expose : activate(), pay(), close(). Each command consults the table. Do not expose setStatus(Status). That is how CLOSED becomes ACTIVE in one assignment, and how a retry “fixes” a ticket that was already finished.

This shape shows up everywhere you will design: ATM sessions, vending machines, elevators, bookings, orders, payments, schedulers. If what the object is allowed to do depends on where it is in its life, you have a state machine. Draw the table before you pick a coding style.

Later you will choose how to code the table: a status enum with guards, or State objects when each state has a page of behavior. The machine comes first. The Gang of Four State pattern is one way to implement a busy machine, not a substitute for drawing the arrows. That choice is state versus status.

The entity owns the table

Repository saves whatever legal status the entity already has. Controller reports the exception. Strategy never hears about status. Factory creates CREATED. Those pockets are from responsibility assignment. The arrows themselves live on Ticket.

java
enum TicketStatus { CREATED, ACTIVE, PAID, CLOSED }

class Ticket {
  private TicketStatus status = TicketStatus.CREATED;

  void activate() { require(TicketStatus.CREATED); status = TicketStatus.ACTIVE; }
  void pay()      { require(TicketStatus.ACTIVE);  status = TicketStatus.PAID; }
  void close()    { require(TicketStatus.PAID);    status = TicketStatus.CLOSED; }

  private void require(TicketStatus expected) {
    if (status != expected) {
      throw new IllegalStateException(status + " cannot move from here");
    }
  }
}

// tickets.get(id).activate() on a CLOSED row throws.
// There is no setStatus. A webhook cannot resurrect the stay.

Where this machine appears

  • Parking ticket — this page’s running example.
  • Order / booking — placed, paid, fulfilled, cancelled.
  • ATM session — no-card, has-card, authenticated, dispensing.
  • Elevator — idle, moving, boarding; doors are not a boolean soup.

What the machine is not

  • Not a pipeline of checks (auth → balance → fraud). That is a chain of steps.
  • Not three flags that can all be true at once.
  • Not setStatus from a controller “just this once.”
  • Not the State pattern until each chapter has real behavior.

Boolean soup versus one field

isPaid, isClosed, isActive can all be true at once. One status field with a table cannot. If you need three flags to know where you are, you needed states. Leftover flags are a machine you refused to name — see object lifecycle.

Flags

parked=true, paid=true, closed=true
Now what is activate() supposed to do?

One status

status == CLOSED
activate() throws. The table is the truth.

Interview walk

  1. List the states on the board. Four words for Ticket.
  2. List the legal arrows and name the commands.
  3. List the illegal arrows clearly. State clearly that CLOSED → ACTIVE is not a retry.
  4. Say the field is private. Say there is no setter.
  5. Only then decide enum-plus-guard versus State objects.

Common mistakes

Public setStatus

A setter is an unmarked door. CLOSED → ACTIVE becomes one assignment. Commands force every change through the table.

Boolean soup

isPaid, isClosed, isActive can all be true at once. One status field with a table cannot. If you need three flags to know where you are, you needed states.

Jumping to the State pattern

CreatedState that only assigns an enum is extra ceremony. Draw the machine first. Open State pattern when each chapter has a page of behavior, as in vending or ATM.

Same machine, other products

Write the four words first, then the illegal arrows, then the commands. The product name changes; the discipline does not. An elevator that is “moving and idle” at once is boolean soup with doors. An ATM that lets dispense run before PIN is a missing require(). A booking that reopens after checkout is CLOSED → ACTIVE with a nicer label.

Booking

HELD → CONFIRMED → CHECKED_IN → COMPLETE. No COMPLETE → HELD.

ATM session

NO_CARD → HAS_CARD → AUTH → DISPENSING. No DISPENSING without AUTH.

Order

PLACED → PAID → SHIPPED → DELIVERED. Refund is a new row, not a reverse.

Private field, public questions

isClosed() is allowed. setStatus is not. Readers may ask where the ticket is. Writers must go through activate, pay, and close. Keeping transitions inside the type is how you encapsulate the machine.

Read next

Takeaway

Write the states and the arrows. Ticket is CREATED → ACTIVE → PAID → CLOSED. CLOSED → ACTIVE is illegal. Commands, not setters. The machine is the spec; enum versus State objects is a later spelling.