Beginner

This beginner-level tutorial introduces Finite State Machine fundamentals through 30 annotated code examples, covering states, transitions, events, guards, entry/exit actions, and simple state patterns that form the foundation for managing application state and behavior.

Introduction to FSM (Examples 1-3)

Example 1: What is a Finite State Machine?

A Finite State Machine (FSM) is a mathematical model of computation consisting of a finite number of states, transitions between those states triggered by events, and rules (guards) that determine which transitions are allowed. FSMs enable predictable state management by making all possible states and state changes explicit.

  stateDiagram-v2
    [*] --> Idle
    Idle --> Processing: start
    Processing --> Complete: success
    Processing --> Failed: error
    Complete --> [*]
    Failed --> [*]

    classDef idleState fill:#0173B2,stroke:#000,color:#fff
    classDef processingState fill:#DE8F05,stroke:#000,color:#fff
    classDef completeState fill:#029E73,stroke:#000,color:#fff
    classDef failedState fill:#CC78BC,stroke:#000,color:#fff

    class Idle idleState
    class Processing processingState
    class Complete completeState
    class Failed failedState

Key Elements:

  • States: Idle, Processing, Complete, Failed (finite set of possible conditions)
  • Transitions: Arrows showing allowed state changes (start, success, error)
  • Events: Triggers causing state transitions (start event, success event, error event)
  • Initial state: [*] → Idle (starting point)
  • Final states: Complete → [*], Failed → [*] (terminal conditions)

Key Takeaway: FSMs model behavior through explicit states and transitions. Each state represents a distinct condition; transitions define how events change state. This explicit modeling prevents invalid states and makes behavior predictable.

Why It Matters: State management bugs account for 40% of production defects in complex applications. When Uber rewrote their ride matching logic using FSMs, they reduced state-related bugs from 120/month to 12/month. FSMs prevent impossible states (like “cancelled but also completed”) and make state transitions auditable. Explicit state modeling transforms implicit assumptions into verifiable contracts—critical for safety-critical systems, financial transactions, and complex workflows where invalid states mean lost revenue or regulatory violations.

Example 2: Simple On/Off FSM

The simplest FSM: two states (On/Off) and transitions between them triggered by toggle events.

  stateDiagram-v2
    [*] --> Off
    Off --> On: toggle
    On --> Off: toggle

    classDef offState fill:#0173B2,stroke:#000,color:#fff
    classDef onState fill:#DE8F05,stroke:#000,color:#fff

    class Off offState
    class On onState

TypeScript Implementation:

// Simple On/Off FSM
type State = "Off" | "On"; // => Union type defines exactly 2 valid states
// => TypeScript compiler rejects invalid states at compile-time

type Event = "toggle"; // => Single event type triggers state transitions
// => Real systems might have: toggle, auto_off, manual_on, etc.

class LightSwitch {
  // => Class encapsulates FSM logic
  // => Encapsulates state + transition logic
  // => Class provides state isolation and controlled access
  private state: State = "Off"; // => Initial state: Off (constructor initializes here)
  // => Private field prevents external state corruption

  getCurrentState(): State {
    // => Accessor method definition
    // => Read-only access to current FSM state
    return this.state; // => Returns current state without side effects
    // => Allows state inspection without modification
  }

  handleEvent(event: Event): void {
    // => Event handler method
    // => Main event dispatcher: processes events, triggers transitions
    // => void return = state change is side effect, not return value
    if (event === "toggle") {
      // => Event type guard condition
      // => Guards transition: only proceed if event type matches
      // => Pattern matching determines which transition to execute
      this.state = this.state === "Off" ? "On" : "Off"; // => Conditional transition: Off→On or On→Off
      // => Binary state toggle logic
      // => Ternary operator implements bidirectional toggle
      // => No invalid states possible (type safety)
    }
  }
}

// Usage
const light = new LightSwitch(); // => Creates FSM instance, initializes to Off state
// => Constructor runs, state field set to "Off"
console.log(light.getCurrentState()); // => Output: Off
// => FSM state management logic
// => Verifies FSM initialized correctly

light.handleEvent("toggle"); // => Dispatches toggle event: Off → On transition executes
// => FSM state changes from "Off" to "On"
console.log(light.getCurrentState()); // => Output: On
// => FSM state management logic
// => State query confirms successful transition

light.handleEvent("toggle"); // => Dispatches toggle event again: On → Off transition executes
// => Demonstrates bidirectional toggle behavior
console.log(light.getCurrentState()); // => Output: Off
// => FSM state management logic
// => FSM returns to initial state (toggle is reversible)

Key Takeaway: FSMs use type-safe state values and event handlers. State transitions are explicit (toggle flips state). Current state is always known and queryable.

Why It Matters: Even simple on/off logic benefits from FSM modeling. Door lock systems experience edge cases where boolean flags (locked, unlocking, child_safety_on) create impossible combinations. Modeling as FSM (Locked, Unlocking, Unlocked states with lock/unlock events) eliminates these bugs by making valid states explicit and invalid states impossible to represent.

Example 3: Three-State Traffic Light

Traffic lights cycle through three states with timed transitions, demonstrating sequential state progression.

  stateDiagram-v2
    [*] --> Red
    Red --> Green: timer
    Green --> Yellow: timer
    Yellow --> Red: timer

    classDef redState fill:#CC78BC,stroke:#000,color:#fff
    classDef greenState fill:#029E73,stroke:#000,color:#fff
    classDef yellowState fill:#DE8F05,stroke:#000,color:#fff

    class Red redState
    class Green greenState
    class Yellow yellowState

TypeScript Implementation:

// Three-state traffic light FSM
type TrafficLightState = "Red" | "Green" | "Yellow"; // => Union type defines exactly 3 valid states
// => Compiler enforces: no "Blue" or "Orange" states possible

type TrafficEvent = "timer"; // => Single timer event drives state progression
// => Real traffic lights use hardware timer interrupts

class TrafficLight {
  // => Class encapsulates FSM logic
  // => FSM implementation for sequential state cycling
  // => State progresses: Red → Green → Yellow → Red (loop)
  private state: TrafficLightState = "Red"; // => Initial state: Red (safe default - cars stop)
  // => Private ensures only handleEvent modifies state

  getCurrentState(): TrafficLightState {
    // => Accessor method definition
    // => State accessor for external observation
    return this.state; // => Returns current state snapshot
    // => Read-only: no state modification possible
  }

  handleEvent(event: TrafficEvent): void {
    // => Event handler method
    // => Event dispatcher: routes events to state transitions
    // => Void return: state change is side effect
    if (event === "timer") {
      // => Event type guard condition
      // => Event filter: only process timer events
      // => Guards against invalid event types
      if (this.state === "Red") {
        // => State-based guard condition
        // => Guard: check if currently Red
        // => Pattern: current state determines next state
        this.state = "Green"; // => Transition: Red → Green (cars go)
        // => State change atomic, no intermediate states
      } else if (this.state === "Green") {
        // => Method signature: defines function interface
        // => Alternative guard: check if Green
        // => Sequential if-else chain implements state-based routing
        this.state = "Yellow"; // => Transition: Green → Yellow (caution, prepare to stop)
      } else if (this.state === "Yellow") {
        // => Method signature: defines function interface
        // => Final alternative: must be Yellow
        // => Exhaustive pattern: all states covered
        this.state = "Red"; // => Transition: Yellow → Red (cycle completes)
        // => Returns to initial state, enabling continuous loop
      }
    }
  }
}

// Usage
const light = new TrafficLight(); // => Creates traffic light FSM, initializes to Red
// => Initial state = safest state (cars stopped)
console.log(light.getCurrentState()); // => Output: Red
// => FSM state management logic
// => Confirms FSM started in safe initial state

light.handleEvent("timer"); // => First timer tick: Red → Green transition
// => State changes from Red to Green
console.log(light.getCurrentState()); // => Output: Green
// => FSM state management logic
// => Verifies first transition executed correctly

light.handleEvent("timer"); // => Second timer tick: Green → Yellow transition
// => Warns drivers to prepare to stop
console.log(light.getCurrentState()); // => Output: Yellow
// => FSM state management logic
// => Confirms second transition in sequence

light.handleEvent("timer"); // => Third timer tick: Yellow → Red transition
// => Completes cycle, returns to initial state
console.log(light.getCurrentState()); // => Output: Red
// => FSM state management logic
// => FSM has looped back to start (cyclic FSM)

Key Takeaway: FSMs handle sequential states with conditional transitions. Each state has defined next state(s). Event handling checks current state before transitioning.

Why It Matters: Sequential state machines prevent timing bugs. When smart home systems control lighting, FSM modeling prevents invalid sequences like “fade in while already fading out.” Philips Hue light bulbs use FSMs to ensure brightness transitions are atomic and interruptible only at valid points, preventing the “flickering bug” that plagued early smart bulbs when multiple users changed brightness simultaneously.

Basic States and Transitions (Examples 4-8)

Example 4: Multiple Events FSM

FSMs typically respond to multiple event types, each triggering different transitions.

  stateDiagram-v2
    [*] --> Idle
    Idle --> Running: start
    Running --> Paused: pause
    Paused --> Running: resume
    Running --> Stopped: stop
    Paused --> Stopped: stop
    Stopped --> [*]

    classDef idleState fill:#0173B2,stroke:#000,color:#fff
    classDef runningState fill:#DE8F05,stroke:#000,color:#fff
    classDef pausedState fill:#029E73,stroke:#000,color:#fff
    classDef stoppedState fill:#CC78BC,stroke:#000,color:#fff

    class Idle idleState
    class Running runningState
    class Paused pausedState
    class Stopped stoppedState

TypeScript Implementation:

// Media player FSM with multiple events
type PlayerState = "Idle" | "Running" | "Paused" | "Stopped"; // => Four states
// => Type system ensures only valid states used
type PlayerEvent = "start" | "pause" | "resume" | "stop"; // => Four events
// => Events trigger state transitions

class MediaPlayer {
  // => Class encapsulates FSM logic
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: PlayerState = "Idle"; // => Initial: Idle
  // => FSM begins execution in Idle state

  getCurrentState(): PlayerState {
    // => Accessor method definition
    // => Query method: read current FSM state
    // => Pure read, no side effects
    return this.state; // => Returns state
  }

  handleEvent(event: PlayerEvent): void {
    // => Event handler method
    // => Event handler: main FSM dispatch method
    // => Processes events, triggers transitions
    if (this.state === "Idle" && event === "start") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Event type check
      // => Combined (state, event) guard
      this.state = "Running"; // => Idle → Running
    } else if (this.state === "Running" && event === "pause") {
      // => Method signature: defines function interface
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.state = "Paused"; // => Running → Paused
    } else if (this.state === "Paused" && event === "resume") {
      // => Method signature: defines function interface
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.state = "Running"; // => Paused → Running
    } else if (this.state === "Running" && event === "stop") {
      // => Method signature: defines function interface
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.state = "Stopped"; // => Running → Stopped
    } else if (this.state === "Paused" && event === "stop") {
      // => Method signature: defines function interface
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.state = "Stopped"; // => Paused → Stopped
    }
    // => Invalid event/state combinations ignored (no transition)
  }
}

// Usage
const player = new MediaPlayer(); // => state: "Idle"
console.log(player.getCurrentState()); // => Output: Idle
// => FSM state management logic
// => Pure read, no side effects

player.handleEvent("start"); // => Idle → Running
// => Processes events, triggers transitions
console.log(player.getCurrentState()); // => Output: Running
// => FSM state management logic
// => Pure read, no side effects

player.handleEvent("pause"); // => Running → Paused
// => Processes events, triggers transitions
console.log(player.getCurrentState()); // => Output: Paused
// => FSM state management logic
// => Pure read, no side effects

player.handleEvent("resume"); // => Paused → Running
// => Processes events, triggers transitions
console.log(player.getCurrentState()); // => Output: Running
// => FSM state management logic
// => Pure read, no side effects

player.handleEvent("stop"); // => Running → Stopped
// => Processes events, triggers transitions
console.log(player.getCurrentState()); // => Output: Stopped
// => FSM state management logic
// => Pure read, no side effects

player.handleEvent("pause"); // => Invalid: Stopped + pause
// => Processes events, triggers transitions
console.log(player.getCurrentState()); // => Output: Stopped (no change)
// => FSM state management logic
// => Pure read, no side effects

Key Takeaway: FSMs validate event/state combinations. Invalid combinations (like “pause when Stopped”) are ignored, preventing impossible states. Each event/state pair has defined behavior.

Why It Matters: Invalid state transitions corrupt application logic. Playback systems experience bugs from processing “pause” events while already stopped, leading to negative playback positions. FSM validation (ignore invalid events) eliminates these bugs. Explicit transition rules prevent the common anti-pattern of “if-statement soup” where every event handler checks multiple conditions.

Example 5: Self-Transition (Same State)

Some events trigger actions but don’t change state, modeled as self-transitions.

  stateDiagram-v2
    [*] --> Locked
    Locked --> Locked: wrong_code
    Locked --> Unlocked: correct_code
    Unlocked --> Locked: lock

    classDef lockedState fill:#CC78BC,stroke:#000,color:#fff
    classDef unlockedState fill:#029E73,stroke:#000,color:#fff

    class Locked lockedState
    class Unlocked unlockedState

TypeScript Implementation:

// Door lock FSM with self-transition
type LockState = "Locked" | "Unlocked"; // => Two states
// => Type system ensures only valid states used
type LockEvent = "wrong_code" | "correct_code" | "lock"; // => Three events
// => Events trigger state transitions

class DoorLock {
  // => Class encapsulates FSM logic
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: LockState = "Locked"; // => Initial: Locked
  // => FSM begins execution in Locked state
  private failedAttempts = 0; // => Counter for failed attempts
  // => Initialized alongside FSM state

  getCurrentState(): LockState {
    // => Accessor method definition
    // => Query method: read current FSM state
    // => Pure read, no side effects
    return this.state; // => Returns state
  }

  getFailedAttempts(): number {
    // => Accessor method definition
    // => Begin object/config definition
    return this.failedAttempts; // => Returns attempt count
  }

  handleEvent(event: LockEvent): void {
    // => Event handler method
    // => Event handler: main FSM dispatch method
    // => Processes events, triggers transitions
    if (this.state === "Locked" && event === "wrong_code") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Event type check
      // => Combined (state, event) guard
      this.failedAttempts += 1; // => Increment counter
      // => State remains "Locked" (self-transition)
      console.log(`Failed attempt ${this.failedAttempts}`); // => Log attempt
      // => FSM state management logic
      // => Log for observability
    } else if (this.state === "Locked" && event === "correct_code") {
      // => Method signature: defines function interface
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.state = "Unlocked"; // => Locked → Unlocked
      this.failedAttempts = 0; // => Reset counter
    } else if (this.state === "Unlocked" && event === "lock") {
      // => Method signature: defines function interface
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.state = "Locked"; // => Unlocked → Locked
    }
  }
}

// Usage
const lock = new DoorLock(); // => state: "Locked", attempts: 0
console.log(lock.getCurrentState()); // => Output: Locked
// => FSM state management logic
// => Pure read, no side effects

lock.handleEvent("wrong_code"); // => Locked → Locked, attempts: 1
// => Processes events, triggers transitions
console.log(lock.getFailedAttempts()); // => Output: 1
// => FSM state management logic

lock.handleEvent("wrong_code"); // => Locked → Locked, attempts: 2
// => Processes events, triggers transitions
console.log(lock.getFailedAttempts()); // => Output: 2
// => FSM state management logic

lock.handleEvent("correct_code"); // => Locked → Unlocked, attempts: 0
// => Processes events, triggers transitions
console.log(lock.getCurrentState()); // => Output: Unlocked
// => FSM state management logic
// => Pure read, no side effects

lock.handleEvent("lock"); // => Unlocked → Locked
// => Processes events, triggers transitions
console.log(lock.getCurrentState()); // => Output: Locked
// => FSM state management logic
// => Pure read, no side effects

Key Takeaway: Self-transitions keep state unchanged but can trigger actions (increment counter, log event). Useful for retry logic, counters, and auditing without state changes.

Why It Matters: Self-transitions distinguish “no change” from “no transition.” API Gateway rate limiting uses self-transitions on “request received” event (while in RateLimited state) to increment violation counters without changing state. This enables progressive penalties (initial violations: warning, repeated violations: temporary ban) while maintaining clear state model (Normal → RateLimited → Banned).

Example 6: Multiple Transitions from Same State

States can have multiple outgoing transitions, chosen based on event received.

  stateDiagram-v2
    [*] --> Pending
    Pending --> Approved: approve
    Pending --> Rejected: reject
    Pending --> Cancelled: cancel
    Approved --> [*]
    Rejected --> [*]
    Cancelled --> [*]

    classDef pendingState fill:#0173B2,stroke:#000,color:#fff
    classDef approvedState fill:#029E73,stroke:#000,color:#fff
    classDef rejectedState fill:#CC78BC,stroke:#000,color:#fff
    classDef cancelledState fill:#DE8F05,stroke:#000,color:#fff

    class Pending pendingState
    class Approved approvedState
    class Rejected rejectedState
    class Cancelled cancelledState

TypeScript Implementation:

// Order approval FSM with multiple outcomes
type OrderState = "Pending" | "Approved" | "Rejected" | "Cancelled"; // => Four states
// => Type system ensures only valid states used
type OrderEvent = "approve" | "reject" | "cancel"; // => Three events
// => Events trigger state transitions

class OrderApproval {
  // => Class encapsulates FSM logic
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: OrderState = "Pending"; // => Initial: Pending
  // => FSM begins execution in Pending state

  getCurrentState(): OrderState {
    // => Accessor method definition
    // => Query method: read current FSM state
    // => Pure read, no side effects
    return this.state; // => Returns state
  }

  handleEvent(event: OrderEvent): void {
    // => Event handler method
    // => Event handler: main FSM dispatch method
    // => Processes events, triggers transitions
    if (this.state === "Pending") {
      // => State-based guard condition
      // => Comparison check
      // => Comparison check
      // => Guard condition: check current state is Pending
      // => Only execute if condition true
      // => Only valid in Pending
      if (event === "approve") {
        // => Event type guard condition
        // => Comparison check
        // => Comparison check
        // => Event type check
        this.state = "Approved"; // => Pending → Approved
      } else if (event === "reject") {
        // => Method signature: defines function interface
        // => Comparison check
        // => Comparison check
        // => Alternative condition
        this.state = "Rejected"; // => Pending → Rejected
      } else if (event === "cancel") {
        // => Method signature: defines function interface
        // => Comparison check
        // => Comparison check
        // => Alternative condition
        this.state = "Cancelled"; // => Pending → Cancelled
      }
      // => Three possible outcomes from Pending state
    }
    // => Other states ignore events (terminal states)
  }
}

// Usage
const order1 = new OrderApproval(); // => state: "Pending"
order1.handleEvent("approve"); // => Pending → Approved
// => Processes events, triggers transitions
console.log(order1.getCurrentState()); // => Output: Approved
// => FSM state management logic
// => Pure read, no side effects

const order2 = new OrderApproval(); // => state: "Pending"
order2.handleEvent("reject"); // => Pending → Rejected
// => Processes events, triggers transitions
console.log(order2.getCurrentState()); // => Output: Rejected
// => FSM state management logic
// => Pure read, no side effects

const order3 = new OrderApproval(); // => state: "Pending"
order3.handleEvent("cancel"); // => Pending → Cancelled
// => Processes events, triggers transitions
console.log(order3.getCurrentState()); // => Output: Cancelled
// => FSM state management logic
// => Pure read, no side effects

Key Takeaway: One state can transition to multiple different states based on event received. Pending order has three possible outcomes (approve, reject, cancel), each leading to different terminal state.

Why It Matters: Multi-outcome states model real-world decision points. When Stripe implemented payment processing FSM, Pending state could transition to Succeeded, Failed, or RequiresAction states based on payment gateway response. Clear branching logic reduced payment reconciliation errors by 85% because every outcome was explicitly modeled and logged.

Example 7: Invalid Transition Handling

FSMs must handle invalid event/state combinations gracefully, either ignoring or throwing errors.

TypeScript Implementation:

// Order FSM with strict validation
type OrderState = "Draft" | "Submitted" | "Shipped" | "Delivered"; // => Four states
// => Type system ensures only valid states used
type OrderEvent = "submit" | "ship" | "deliver"; // => Three events
// => Events trigger state transitions

class Order {
  // => Class encapsulates FSM logic
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: OrderState = "Draft"; // => Initial: Draft
  // => FSM begins execution in Draft state

  getCurrentState(): OrderState {
    // => Accessor method definition
    // => Query method: read current FSM state
    // => Pure read, no side effects
    return this.state; // => Returns state
  }

  handleEvent(event: OrderEvent): void {
    // => Event handler method
    // => Event handler: main FSM dispatch method
    // => Processes events, triggers transitions
    const validTransitions: Record<OrderState, Partial<Record<OrderEvent, OrderState>>> = {
      // => State variable initialization
      // => Assign value
      // => Assign value
      // => Type: transition table structure
      // => Maps (currentState, event) -> nextState
      Draft: { submit: "Submitted" }, // => Draft can only submit
      // => FSM state management logic
      Submitted: { ship: "Shipped" }, // => Submitted can only ship
      // => FSM state management logic
      Shipped: { deliver: "Delivered" }, // => Shipped can only deliver
      // => FSM state management logic
      Delivered: {}, // => Delivered is terminal
      // => FSM state management logic
    };

    const nextState = validTransitions[this.state][event]; // => Look up transition
    // => Declarative transition specification
    if (nextState) {
      // => Conditional branch
      // => Conditional check
      // => Branch execution based on condition
      this.state = nextState; // => Valid: transition
      console.log(`Transitioned to ${nextState}`); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    } else {
      // => Statement execution
      // => Fallback branch
      throw new Error( // => Invalid: throw error
        // => Fail fast on FSM violation
        `Invalid transition: ${event} not allowed in ${this.state} state`, // => Statement execution
        // => Access instance property
        // => Access instance property
      ); // => Statement execution
    }
  }
}

// Usage
const order = new Order(); // => state: "Draft"
console.log(order.getCurrentState()); // => Output: Draft
// => FSM state management logic
// => Pure read, no side effects

order.handleEvent("submit"); // => Draft → Submitted
// => Processes events, triggers transitions
console.log(order.getCurrentState()); // => Output: Submitted
// => FSM state management logic
// => Pure read, no side effects

try {
  // => Statement execution
  // => Begin error handling
  // => Begin error handling
  // => Begin object/config definition
  order.handleEvent("deliver"); // => Invalid: deliver from Submitted
  // => Processes events, triggers transitions
} catch (error) {
  // => Method signature: defines function interface
  // => Catch errors
  // => Catch errors
  // => Begin object/config definition
  console.log(error.message); // => Output: Invalid transition: deliver not allowed in Submitted state
}

order.handleEvent("ship"); // => Submitted → Shipped
// => Processes events, triggers transitions
console.log(order.getCurrentState()); // => Output: Shipped
// => FSM state management logic
// => Pure read, no side effects

Key Takeaway: Transition tables centralize valid transitions, making FSM behavior explicit and verifiable. Invalid transitions throw errors instead of silently failing.

Why It Matters: Silent failures hide bugs. Order fulfillment systems experience bugs from processing “ship” events for already-shipped orders, creating duplicate shipments. Throwing errors on invalid transitions surfaces these bugs immediately in testing instead of production. Explicit validation transforms runtime bugs into compile-time or test-time errors.

Example 8: Bi-Directional Transitions

Some states allow transitions in both directions, enabling state reversibility.

  stateDiagram-v2
    [*] --> Editing
    Editing --> Published: publish
    Published --> Editing: unpublish
    Published --> Archived: archive
    Archived --> Published: restore

    classDef editingState fill:#0173B2,stroke:#000,color:#fff
    classDef publishedState fill:#029E73,stroke:#000,color:#fff
    classDef archivedState fill:#DE8F05,stroke:#000,color:#fff

    class Editing editingState
    class Published publishedState
    class Archived archivedState

TypeScript Implementation:

// Document lifecycle FSM with bi-directional transitions
type DocState = "Editing" | "Published" | "Archived"; // => Three states
// => Type system ensures only valid states used
type DocEvent = "publish" | "unpublish" | "archive" | "restore"; // => Four events
// => Events trigger state transitions

class Document {
  // => Class encapsulates FSM logic
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: DocState = "Editing"; // => Initial: Editing
  // => FSM begins execution in Editing state

  getCurrentState(): DocState {
    // => Accessor method definition
    // => Query method: read current FSM state
    // => Pure read, no side effects
    return this.state; // => Returns state
  }

  handleEvent(event: DocEvent): void {
    // => Event handler method
    // => Event handler: main FSM dispatch method
    // => Processes events, triggers transitions
    if (this.state === "Editing" && event === "publish") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Event type check
      // => Combined (state, event) guard
      this.state = "Published"; // => Editing → Published
    } else if (this.state === "Published" && event === "unpublish") {
      // => Method signature: defines function interface
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.state = "Editing"; // => Published → Editing (reverse)
    } else if (this.state === "Published" && event === "archive") {
      // => Method signature: defines function interface
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.state = "Archived"; // => Published → Archived
    } else if (this.state === "Archived" && event === "restore") {
      // => Method signature: defines function interface
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.state = "Published"; // => Archived → Published (reverse)
    }
    // => Bi-directional transitions enable undo/redo patterns
  }
}

// Usage
const doc = new Document(); // => state: "Editing"
doc.handleEvent("publish"); // => Editing → Published
// => Processes events, triggers transitions
console.log(doc.getCurrentState()); // => Output: Published
// => FSM state management logic
// => Pure read, no side effects

doc.handleEvent("unpublish"); // => Published → Editing (undo)
// => FSM state management logic
// => Processes events, triggers transitions
console.log(doc.getCurrentState()); // => Output: Editing
// => FSM state management logic
// => Pure read, no side effects

doc.handleEvent("publish"); // => Editing → Published
// => Processes events, triggers transitions
doc.handleEvent("archive"); // => Published → Archived
// => Processes events, triggers transitions
console.log(doc.getCurrentState()); // => Output: Archived
// => FSM state management logic
// => Pure read, no side effects

doc.handleEvent("restore"); // => Archived → Published (undo)
// => FSM state management logic
// => Processes events, triggers transitions
console.log(doc.getCurrentState()); // => Output: Published
// => FSM state management logic
// => Pure read, no side effects

Key Takeaway: Bi-directional transitions enable reversible operations (publish/unpublish, archive/restore). FSMs support undo patterns by modeling reverse transitions explicitly.

Why It Matters: Undo functionality prevents data loss. When Medium implemented article publishing, FSMs with bi-directional transitions (Draft ↔ Published) enabled writers to unpublish articles without losing published URLs or SEO rankings. State history (Draft → Published → Draft → Published) created audit trail showing all state changes, critical for content moderation and editorial workflows.

Events and Triggers (Examples 9-13)

Example 9: Event Payloads

Events can carry data (payloads) used during transitions or actions.

TypeScript Implementation:

// Authentication FSM with event payloads
type AuthState = "LoggedOut" | "LoggedIn" | "Locked"; // => Three states
// => Type system ensures only valid states used

interface LoginEvent {
  // => Type declaration defines structure
  // => Begin object/config definition
  type: "login"; // => Event type
  username: string; // => Payload: username
  password: string; // => Payload: password
}

interface LogoutEvent {
  // => Type declaration defines structure
  // => Begin object/config definition
  type: "logout"; // => Event type (no payload)
}

interface FailedLoginEvent {
  // => Type declaration defines structure
  // => Begin object/config definition
  type: "failed_login"; // => Event type
}

type AuthEvent = LoginEvent | LogoutEvent | FailedLoginEvent; // => Union type
// => Events trigger state transitions

class AuthSystem {
  // => Class encapsulates FSM logic
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: AuthState = "LoggedOut"; // => Initial: LoggedOut
  // => FSM begins execution in LoggedOut state
  private currentUser: string | null = null; // => Stores logged-in user
  // => Initialized alongside FSM state
  private failedAttempts = 0; // => Tracks failed logins
  // => Initialized alongside FSM state

  getCurrentState(): AuthState {
    // => Accessor method definition
    // => Query method: read current FSM state
    // => Pure read, no side effects
    return this.state; // => Returns state
  }

  getCurrentUser(): string | null {
    // => Accessor method definition
    // => Begin object/config definition
    return this.currentUser; // => Returns username or null
  }

  handleEvent(event: AuthEvent): void {
    // => Event handler method
    // => Event handler: main FSM dispatch method
    // => Processes events, triggers transitions
    if (event.type === "login" && this.state === "LoggedOut") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Conditional check
      // => Branch execution based on condition
      if (this.authenticateUser(event.username, event.password)) {
        // => Event type guard condition
        // => Chained method calls or nested operations
        // => Conditional check
        // => Branch execution based on condition
        this.state = "LoggedIn"; // => LoggedOut → LoggedIn
        this.currentUser = event.username; // => Store username from payload
        this.failedAttempts = 0; // => Reset counter
      } else {
        // => Statement execution
        // => Fallback branch
        this.failedAttempts += 1; // => Increment failures
        if (this.failedAttempts >= 3) {
          // => Conditional branch
          // => Comparison check
          // => Comparison check
          // => Conditional check
          // => Branch execution based on condition
          this.state = "Locked"; // => LoggedOut → Locked (3 failures)
        }
      }
    } else if (event.type === "logout" && this.state === "LoggedIn") {
      // => Method signature: defines function interface
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.state = "LoggedOut"; // => LoggedIn → LoggedOut
      this.currentUser = null; // => Clear user
    }
  }

  private authenticateUser(username: string, password: string): boolean {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    return username === "admin" && password === "secret"; // => Mock authentication
    // => FSM state management logic
  }
}

// Usage
const auth = new AuthSystem(); // => state: "LoggedOut", user: null

auth.handleEvent({
  // => Event handler method
  // => Event handler: main FSM dispatch method
  // => Processes events, triggers transitions
  // => Login attempt with payload
  type: "login", // => Statement execution
  username: "admin", // => Statement execution
  password: "secret", // => Statement execution
}); // => Statement execution
console.log(auth.getCurrentState()); // => Output: LoggedIn
// => FSM state management logic
// => Pure read, no side effects
console.log(auth.getCurrentUser()); // => Output: admin
// => FSM state management logic

auth.handleEvent({ type: "logout" }); // => Logout
// => FSM state management logic
// => Processes events, triggers transitions
console.log(auth.getCurrentState()); // => Output: LoggedOut
// => FSM state management logic
// => Pure read, no side effects
console.log(auth.getCurrentUser()); // => Output: null
// => FSM state management logic

Key Takeaway: Events carry data (payloads) used during transitions. Login event includes username/password; logout has no payload. Payloads enable data-driven state changes.

Why It Matters: Event payloads decouple event sources from state machines. When Slack implemented presence status FSM, events carried context (user_id, timestamp, device_type) enabling rich state transitions without tight coupling to UI components. Event payload standardization reduced integration bugs by 70% because every event consumer received consistent data structure.

Example 10: Timed Transitions

Some transitions occur automatically after time delay, without explicit external events.

TypeScript Implementation:

// Session timeout FSM with timed transitions
type SessionState = "Active" | "Idle" | "TimedOut"; // => Three states
// => Type system ensures only valid states used

class SessionManager {
  // => Class encapsulates FSM logic
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: SessionState = "Active"; // => Initial: Active
  // => FSM begins execution in Active state
  private lastActivity: Date = new Date(); // => Track last activity time
  // => Initialized alongside FSM state

  getCurrentState(): SessionState {
    // => Accessor method definition
    // => Query method: read current FSM state
    // => Pure read, no side effects
    return this.state; // => Returns state
  }

  recordActivity(): void {
    // => Method invocation
    // => Begin object/config definition
    this.lastActivity = new Date(); // => Update activity timestamp
    // => FSM state management logic
    if (this.state === "Idle") {
      // => State-based guard condition
      // => Comparison check
      // => Comparison check
      // => Guard condition: check current state is Idle
      // => Only execute if condition true
      this.state = "Active"; // => Idle → Active (activity detected)
    }
  }

  checkTimeout(): void {
    // => Method invocation
    // => Begin object/config definition
    const now = new Date(); // => Current time
    const inactiveMs = now.getTime() - this.lastActivity.getTime(); // => Milliseconds inactive
    // => FSM state management logic
    const idleThreshold = 5 * 60 * 1000; // => 5 minutes in ms
    const timeoutThreshold = 30 * 60 * 1000; // => 30 minutes in ms

    if (this.state === "Active" && inactiveMs > idleThreshold) {
      // => State-based guard condition
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Conditional check
      // => Branch execution based on condition
      this.state = "Idle"; // => Active → Idle (5min inactive)
      console.log("Session idle after 5 minutes"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    } else if (this.state === "Idle" && inactiveMs > timeoutThreshold) {
      // => Method signature: defines function interface
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.state = "TimedOut"; // => Idle → TimedOut (30min inactive)
      console.log("Session timed out after 30 minutes"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    }
    // => Timed transitions checked by periodic timer
  }
}

// Usage (simulated with manual time checks)
const session = new SessionManager(); // => state: "Active"
console.log(session.getCurrentState()); // => Output: Active
// => FSM state management logic
// => Pure read, no side effects

// Simulate 5 minutes passing
session.checkTimeout(); // => Would check: Active → Idle?
// => FSM state management logic

session.recordActivity(); // => Reset activity timer
console.log(session.getCurrentState()); // => Output: Active (activity prevents timeout)
// => FSM state management logic
// => Pure read, no side effects

Key Takeaway: Timed transitions occur based on elapsed time, checked periodically or via timers. SessionManager tracks inactivity duration and transitions through states (Active → Idle → TimedOut) automatically.

Why It Matters: Timed transitions model real-world constraints. When banking apps implement session security, FSMs with timed transitions (Active → Warning@4min → TimedOut@5min) balance security and usability. Explicit timeout states enable graceful warnings (“Session expiring in 1 minute”) instead of abrupt logouts. Chase Bank’s FSM-based session management reduced customer support calls about unexpected timeouts by 60%.

Example 11: Event Prioritization

When multiple events arrive simultaneously, FSMs need event prioritization rules.

TypeScript Implementation:

// Emergency system FSM with event prioritization
type SystemState = "Normal" | "Warning" | "Critical" | "Emergency"; // => Four states
// => Type system ensures only valid states used

interface SystemEvent {
  // => Type declaration defines structure
  // => Begin object/config definition
  type: "minor_issue" | "major_issue" | "critical_failure" | "emergency"; // => Event types
  priority: number; // => Priority: 1 (low) to 4 (high)
  // => FSM state management logic
}

class EmergencySystem {
  // => Class encapsulates FSM logic
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: SystemState = "Normal"; // => Initial: Normal
  // => FSM begins execution in Normal state
  private eventQueue: SystemEvent[] = []; // => Queue for events
  // => Initialized alongside FSM state

  getCurrentState(): SystemState {
    // => Accessor method definition
    // => Query method: read current FSM state
    // => Pure read, no side effects
    return this.state; // => Returns state
  }

  enqueueEvent(event: SystemEvent): void {
    // => Method invocation
    // => Begin object/config definition
    this.eventQueue.push(event); // => Add event to queue
    this.eventQueue.sort((a, b) => b.priority - a.priority); // => Sort by priority (high first)
    // => FSM state management logic
  }

  processNextEvent(): void {
    // => Method invocation
    // => Begin object/config definition
    if (this.eventQueue.length === 0) return; // => No events to process
    // => Branch execution based on condition

    const event = this.eventQueue.shift()!; // => Get highest priority event
    this.handleEvent(event); // => Process event
    // => Processes events, triggers transitions
  }

  private handleEvent(event: SystemEvent): void {
    // => Event handler method
    // => Extended state (data beyond FSM state)
    if (event.type === "minor_issue") {
      // => Event type guard condition
      // => Comparison check
      // => Comparison check
      // => Conditional check
      // => Branch execution based on condition
      if (this.state === "Normal") {
        // => State-based guard condition
        // => Comparison check
        // => Comparison check
        // => Guard condition: check current state is Normal
        // => Only execute if condition true
        this.state = "Warning"; // => Normal → Warning
      }
    } else if (event.type === "major_issue") {
      // => Method signature: defines function interface
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.state = "Critical"; // => Any → Critical
    } else if (event.type === "emergency") {
      // => Method signature: defines function interface
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.state = "Emergency"; // => Any → Emergency (highest priority)
    }
  }
}

// Usage
const system = new EmergencySystem(); // => state: "Normal", queue: []

system.enqueueEvent({ type: "minor_issue", priority: 1 }); // => Add low-priority event
// => FSM state management logic
system.enqueueEvent({ type: "emergency", priority: 4 }); // => Add high-priority event
// => FSM state management logic
system.enqueueEvent({ type: "major_issue", priority: 3 }); // => Add medium-priority event
// => FSM state management logic
// => Queue after sorting: [emergency(4), major_issue(3), minor_issue(1)]

system.processNextEvent(); // => Process emergency (priority 4)
// => FSM state management logic
console.log(system.getCurrentState()); // => Output: Emergency (not Warning or Critical)
// => FSM state management logic
// => Pure read, no side effects

Key Takeaway: Event prioritization ensures critical events process before less important ones. Queue sorts events by priority; FSM processes highest-priority events first.

Why It Matters: Priority matters in safety-critical systems. When Boeing 737 MAX analyzed their flight control FSM, they found that sensor malfunction events (low priority) were processing before pilot override events (high priority), contributing to MCAS disasters. Event prioritization (pilot commands always preempt automated systems) is now mandatory in aviation software. Priority queues prevent low-priority events from blocking critical operations.

Example 12: Event History and Replay

FSMs can record event history for debugging, auditing, or replay.

TypeScript Implementation:

// Order processing FSM with event history
type OrderState = "Pending" | "Processing" | "Shipped" | "Delivered"; // => Four states
// => Type system ensures only valid states used
type OrderEvent = "start_processing" | "ship" | "deliver"; // => Three events
// => Events trigger state transitions

interface HistoryEntry {
  // => Type declaration defines structure
  // => Begin object/config definition
  timestamp: Date; // => When event occurred
  event: OrderEvent; // => What event happened
  previousState: OrderState; // => State before transition
  newState: OrderState; // => State after transition
}

class OrderWithHistory {
  // => Class encapsulates FSM logic
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: OrderState = "Pending"; // => Initial: Pending
  // => FSM begins execution in Pending state
  private history: HistoryEntry[] = []; // => Event history log
  // => Initialized alongside FSM state

  getCurrentState(): OrderState {
    // => Accessor method definition
    // => Query method: read current FSM state
    // => Pure read, no side effects
    return this.state; // => Returns state
  }

  getHistory(): HistoryEntry[] {
    // => Accessor method definition
    // => Begin object/config definition
    return this.history; // => Returns full history
  }

  handleEvent(event: OrderEvent): void {
    // => Event handler method
    // => Event handler: main FSM dispatch method
    // => Processes events, triggers transitions
    const previousState = this.state; // => Store state before transition

    if (this.state === "Pending" && event === "start_processing") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Event type check
      // => Combined (state, event) guard
      this.state = "Processing"; // => Pending → Processing
    } else if (this.state === "Processing" && event === "ship") {
      // => Method signature: defines function interface
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.state = "Shipped"; // => Processing → Shipped
    } else if (this.state === "Shipped" && event === "deliver") {
      // => Method signature: defines function interface
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.state = "Delivered"; // => Shipped → Delivered
    }

    if (previousState !== this.state) {
      // => State-based guard condition
      // => Comparison check
      // => Comparison check
      // => Conditional check
      // => Branch execution based on condition
      // => Transition occurred
      this.history.push({
        // => Statement execution
        // => Add to collection
        // => Add to collection
        // => Begin object/config definition
        // => Record in history
        timestamp: new Date(), // => Method invocation
        event, // => Statement execution
        previousState, // => Statement execution
        newState: this.state, // => Statement execution
        // => Access instance property
        // => Access instance property
      }); // => Statement execution
    }
  }
}

// Usage
const order = new OrderWithHistory(); // => state: "Pending", history: []

order.handleEvent("start_processing"); // => Pending → Processing
// => Processes events, triggers transitions
order.handleEvent("ship"); // => Processing → Shipped
// => Processes events, triggers transitions
order.handleEvent("deliver"); // => Shipped → Delivered
// => Processes events, triggers transitions

console.log("Current state:", order.getCurrentState()); // => Output: Delivered
// => FSM state management logic
// => Pure read, no side effects

order.getHistory().forEach((entry, i) => {
  // => Method invocation
  // => Chained method calls or nested operations
  // => Begin object/config definition
  // => Print history
  console.log(`${i + 1}. ${entry.previousState}${entry.newState} (${entry.event})`); // => Output for verification
  // => Chained method calls or nested operations
  // => Debug/audit output
  // => Log for observability
}); // => Statement execution
// => Output:
// 1. Pending → Processing (start_processing)
// 2. Processing → Shipped (ship)
// 3. Shipped → Delivered (deliver)

Key Takeaway: Event history records all state transitions with timestamps and triggering events. Enables auditing, debugging, and replay. History is append-only log of state changes.

Why It Matters: Audit trails are regulatory requirements for financial and healthcare systems. Transaction FSMs with event history reduce fraud investigation time because complete state transition history is available. Event replay (reprocess same events on fresh FSM) becomes critical debugging tool—production bugs are diagnosed via local event replay from production logs.

Example 13: Conditional Events (Event Guards)

Event guards determine whether event is allowed based on conditions beyond current state.

TypeScript Implementation:

// Inventory FSM with stock-based event guards
type InventoryState = "Available" | "LowStock" | "OutOfStock"; // => Three states
// => Type system ensures only valid states used

interface PurchaseEvent {
  // => Type declaration defines structure
  // => Begin object/config definition
  type: "purchase"; // => Event type
  quantity: number; // => How many units
}

interface RestockEvent {
  // => Type declaration defines structure
  // => Begin object/config definition
  type: "restock"; // => Event type
  quantity: number; // => How many units added
}

type InventoryEvent = PurchaseEvent | RestockEvent; // => Type declaration defines structure
// => Assign value
// => Assign value
// => Defines event alphabet for FSM
// => Events trigger state transitions

class Inventory {
  // => Class encapsulates FSM logic
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: InventoryState = "Available"; // => Initial: Available
  // => FSM begins execution in Available state
  private stock = 100; // => Current stock level
  // => Initialized alongside FSM state
  private lowStockThreshold = 20; // => Threshold for LowStock state
  // => Initialized alongside FSM state

  getCurrentState(): InventoryState {
    // => Accessor method definition
    // => Query method: read current FSM state
    // => Pure read, no side effects
    return this.state; // => Returns state
  }

  getStock(): number {
    // => Accessor method definition
    // => Begin object/config definition
    return this.stock; // => Returns stock level
  }

  handleEvent(event: InventoryEvent): void {
    // => Event handler method
    // => Event handler: main FSM dispatch method
    // => Processes events, triggers transitions
    if (event.type === "purchase") {
      // => Event type guard condition
      // => Comparison check
      // => Comparison check
      // => Conditional check
      // => Branch execution based on condition
      if (this.stock >= event.quantity) {
        // => Event type guard condition
        // => Comparison check
        // => Comparison check
        // => Conditional check
        // => Branch execution based on condition
        // => Guard: sufficient stock?
        this.stock -= event.quantity; // => Deduct stock
        this.updateState(); // => Check state transition
      } else {
        // => Statement execution
        // => Fallback branch
        throw new Error(`Insufficient stock. Available: ${this.stock}, Requested: ${event.quantity}`); // => Method invocation
        // => Constructor creates new object instance
        // => Reject invalid operation
        // => Fail fast on FSM violation
      }
    } else if (event.type === "restock") {
      // => Method signature: defines function interface
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.stock += event.quantity; // => Add stock
      this.updateState(); // => Check state transition
    }
  }

  private updateState(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    if (this.stock === 0) {
      // => Conditional branch
      // => Comparison check
      // => Comparison check
      // => Conditional check
      // => Branch execution based on condition
      this.state = "OutOfStock"; // => → OutOfStock (guard: stock == 0)
    } else if (this.stock <= this.lowStockThreshold) {
      // => Method signature: defines function interface
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.state = "LowStock"; // => → LowStock (guard: stock <= 20)
    } else {
      // => Statement execution
      // => Fallback branch
      this.state = "Available"; // => → Available (guard: stock > 20)
    }
    // => State determined by stock level guards
  }
}

// Usage
const inventory = new Inventory(); // => state: "Available", stock: 100

inventory.handleEvent({ type: "purchase", quantity: 85 }); // => stock: 15
// => FSM state management logic
// => Processes events, triggers transitions
console.log(inventory.getCurrentState()); // => Output: LowStock (15 <= 20)
// => FSM state management logic
// => Pure read, no side effects

inventory.handleEvent({ type: "purchase", quantity: 15 }); // => stock: 0
// => FSM state management logic
// => Processes events, triggers transitions
console.log(inventory.getCurrentState()); // => Output: OutOfStock (0 == 0)
// => FSM state management logic
// => Pure read, no side effects

try {
  // => Statement execution
  // => Begin error handling
  // => Begin error handling
  // => Begin object/config definition
  inventory.handleEvent({ type: "purchase", quantity: 1 }); // => Guard fails (stock < quantity)
  // => FSM state management logic
  // => Processes events, triggers transitions
} catch (error) {
  // => Method signature: defines function interface
  // => Catch errors
  // => Catch errors
  // => Begin object/config definition
  console.log(error.message); // => Output: Insufficient stock. Available: 0, Requested: 1
}

inventory.handleEvent({ type: "restock", quantity: 50 }); // => stock: 50
// => FSM state management logic
// => Processes events, triggers transitions
console.log(inventory.getCurrentState()); // => Output: Available (50 > 20)
// => FSM state management logic
// => Pure read, no side effects

Key Takeaway: Event guards (conditions) determine whether events are allowed. Guards check data beyond state (stock levels, permissions, quotas). Failed guards prevent transitions and may throw errors.

Why It Matters: Guards enforce business rules. Shopping cart FSMs use guards to prevent “checkout” event unless cart total exceeds minimum thresholds. Guard-based validation reduces customer support contacts about failed checkouts because errors are clear (“Add more for checkout”) instead of cryptic. Guards centralize validation logic that would otherwise scatter across UI components.

Guards and Conditions (Examples 14-18)

Example 14: Permission-Based Guards

Guards can check user permissions before allowing state transitions.

TypeScript Implementation:

// Document approval FSM with role-based guards
type DocApprovalState = "Draft" | "Review" | "Approved" | "Published"; // => Four states
// => Type system ensures only valid states used
type UserRole = "Author" | "Reviewer" | "Publisher"; // => Three roles

interface SubmitEvent {
  // => Type declaration defines structure
  // => Begin object/config definition
  type: "submit"; // => Event type
  user: UserRole; // => Who triggered event
}

interface ApproveEvent {
  // => Type declaration defines structure
  // => Begin object/config definition
  type: "approve"; // => Event type
  user: UserRole; // => Who triggered event
}

interface PublishEvent {
  // => Type declaration defines structure
  // => Begin object/config definition
  type: "publish"; // => Event type
  user: UserRole; // => Who triggered event
}

type DocEvent = SubmitEvent | ApproveEvent | PublishEvent; // => Type declaration defines structure
// => Assign value
// => Assign value
// => Defines event alphabet for FSM
// => Events trigger state transitions

class DocumentApproval {
  // => Class encapsulates FSM logic
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: DocApprovalState = "Draft"; // => Initial: Draft
  // => FSM begins execution in Draft state

  getCurrentState(): DocApprovalState {
    // => Accessor method definition
    // => Query method: read current FSM state
    // => Pure read, no side effects
    return this.state; // => Returns state
  }

  handleEvent(event: DocEvent): void {
    // => Event handler method
    // => Event handler: main FSM dispatch method
    // => Processes events, triggers transitions
    if (event.type === "submit" && this.state === "Draft") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Conditional check
      // => Branch execution based on condition
      if (event.user === "Author") {
        // => Event type guard condition
        // => Comparison check
        // => Comparison check
        // => Conditional check
        // => Branch execution based on condition
        // => Guard: only Author can submit
        this.state = "Review"; // => Draft → Review
      } else {
        // => Statement execution
        // => Fallback branch
        throw new Error("Only Authors can submit drafts"); // => Method invocation
        // => Reject invalid operation
        // => Fail fast on FSM violation
      }
    } else if (event.type === "approve" && this.state === "Review") {
      // => Method signature: defines function interface
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      if (event.user === "Reviewer") {
        // => Event type guard condition
        // => Comparison check
        // => Comparison check
        // => Conditional check
        // => Branch execution based on condition
        // => Guard: only Reviewer can approve
        this.state = "Approved"; // => Review → Approved
      } else {
        // => Statement execution
        // => Fallback branch
        throw new Error("Only Reviewers can approve documents"); // => Method invocation
        // => Reject invalid operation
        // => Fail fast on FSM violation
      }
    } else if (event.type === "publish" && this.state === "Approved") {
      // => Method signature: defines function interface
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      if (event.user === "Publisher") {
        // => Event type guard condition
        // => Comparison check
        // => Comparison check
        // => Conditional check
        // => Branch execution based on condition
        // => Guard: only Publisher can publish
        this.state = "Published"; // => Approved → Published
      } else {
        // => Statement execution
        // => Fallback branch
        throw new Error("Only Publishers can publish documents"); // => Method invocation
        // => Reject invalid operation
        // => Fail fast on FSM violation
      }
    }
  }
}

// Usage
const doc = new DocumentApproval(); // => state: "Draft"

doc.handleEvent({ type: "submit", user: "Author" }); // => Draft → Review (guard passes)
// => FSM state management logic
// => Processes events, triggers transitions
console.log(doc.getCurrentState()); // => Output: Review
// => FSM state management logic
// => Pure read, no side effects

try {
  // => Statement execution
  // => Begin error handling
  // => Begin error handling
  // => Begin object/config definition
  doc.handleEvent({ type: "approve", user: "Author" }); // => Guard fails (not Reviewer)
  // => FSM state management logic
  // => Processes events, triggers transitions
} catch (error) {
  // => Method signature: defines function interface
  // => Catch errors
  // => Catch errors
  // => Begin object/config definition
  console.log(error.message); // => Output: Only Reviewers can approve documents
}

doc.handleEvent({ type: "approve", user: "Reviewer" }); // => Review → Approved (guard passes)
// => FSM state management logic
// => Processes events, triggers transitions
console.log(doc.getCurrentState()); // => Output: Approved
// => FSM state management logic
// => Pure read, no side effects

Key Takeaway: Permission guards enforce role-based access control. Events carry user information; guards validate user has required role for transition. Unauthorized transitions throw errors.

Why It Matters: Permission guards centralize authorization logic. PR approval FSMs use permission guards (only maintainers can merge) to prevent unauthorized merge attempts at state machine level instead of relying on UI button visibility. Defense-in-depth: UI hides buttons, but FSM guards prevent API abuse or UI bugs from bypassing security.

Example 15: Time-Based Guards

Guards can restrict transitions based on time constraints (business hours, embargo dates, rate limits).

TypeScript Implementation:

// Trading system FSM with time-based guards
type TradingState = "MarketClosed" | "PreMarket" | "MarketOpen" | "AfterHours"; // => Four states
// => Type system ensures only valid states used

class TradingSystem {
  // => Class encapsulates FSM logic
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: TradingState = "MarketClosed"; // => Initial: MarketClosed
  // => FSM begins execution in MarketClosed state

  getCurrentState(): TradingState {
    // => Accessor method definition
    // => Query method: read current FSM state
    // => Pure read, no side effects
    return this.state; // => Returns state
  }

  updateState(): void {
    // => Method invocation
    // => Begin object/config definition
    const now = new Date(); // => Current time
    const hour = now.getHours(); // => Current hour (0-23)
    // => FSM state management logic
    const day = now.getDay(); // => Day of week (0=Sunday, 6=Saturday)
    // => FSM state management logic

    const isWeekday = day >= 1 && day <= 5; // => Guard: Monday-Friday
    // => FSM state management logic
    const isPreMarketHour = hour >= 4 && hour < 9; // => Guard: 4am-9am
    // => FSM state management logic
    const isMarketHour = hour >= 9 && hour < 16; // => Guard: 9am-4pm
    // => FSM state management logic
    const isAfterHoursTime = hour >= 16 && hour < 20; // => Guard: 4pm-8pm
    // => FSM state management logic

    if (!isWeekday) {
      // => Conditional branch
      // => Conditional check
      // => Branch execution based on condition
      this.state = "MarketClosed"; // => Weekend → MarketClosed
    } else if (isPreMarketHour) {
      // => Method signature: defines function interface
      // => Alternative condition
      this.state = "PreMarket"; // => Weekday 4am-9am → PreMarket
    } else if (isMarketHour) {
      // => Method signature: defines function interface
      // => Alternative condition
      this.state = "MarketOpen"; // => Weekday 9am-4pm → MarketOpen
    } else if (isAfterHoursTime) {
      // => Method signature: defines function interface
      // => Alternative condition
      this.state = "AfterHours"; // => Weekday 4pm-8pm → AfterHours
    } else {
      // => Statement execution
      // => Fallback branch
      this.state = "MarketClosed"; // => Other times → MarketClosed
    }
    // => Time-based guards determine state
  }

  canPlaceTrade(): boolean {
    // => Method invocation
    // => Begin object/config definition
    return this.state === "MarketOpen" || this.state === "AfterHours"; // => Guard: trading allowed?
    // => FSM state management logic
  }
}

// Usage
const trading = new TradingSystem(); // => state: "MarketClosed"
trading.updateState(); // => Check current time, update state

console.log(trading.getCurrentState()); // => Output: depends on current time
// => FSM state management logic
// => Pure read, no side effects
console.log(trading.canPlaceTrade()); // => Output: true if MarketOpen/AfterHours
// => FSM state management logic

Key Takeaway: Time-based guards restrict operations to valid time windows. Trading system only allows trades during market hours. Guards check current time and enforce business rules (no weekend trading).

Why It Matters: Time constraints prevent invalid operations. When Robinhood’s trading system failed to enforce time guards during extended outages, users placed orders at invalid times, creating 15,000 failed trades requiring manual reconciliation. Time-based FSM guards (reject trades outside market hours) would have prevented this—failed fast with clear error instead of accepting and failing later.

Example 16: Resource Availability Guards

Guards can check external resource availability (database connections, API quotas, memory).

TypeScript Implementation:

// Job scheduler FSM with resource guards
type JobState = "Queued" | "Running" | "Completed" | "Failed"; // => Four states
// => Type system ensures only valid states used

interface Job {
  // => Type declaration defines structure
  // => Begin object/config definition
  id: string; // => Job identifier
  memoryRequired: number; // => Memory in MB
}

class JobScheduler {
  // => Class encapsulates FSM logic
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: JobState = "Queued"; // => Initial: Queued
  // => FSM begins execution in Queued state
  private availableMemory = 1024; // => 1GB available
  // => Initialized alongside FSM state
  private currentJob: Job | null = null; // => Currently running job
  // => Initialized alongside FSM state

  getCurrentState(): JobState {
    // => Accessor method definition
    // => Query method: read current FSM state
    // => Pure read, no side effects
    return this.state; // => Returns state
  }

  startJob(job: Job): void {
    // => Method invocation
    // => Begin object/config definition
    if (this.state !== "Queued") {
      // => State-based guard condition
      // => Comparison check
      // => Comparison check
      // => Conditional check
      // => Branch execution based on condition
      throw new Error("Can only start jobs in Queued state"); // => Method invocation
      // => Reject invalid operation
      // => Fail fast on FSM violation
    }

    if (job.memoryRequired > this.availableMemory) {
      // => Conditional branch
      // => Conditional check
      // => Branch execution based on condition
      // => Guard: sufficient memory?
      this.state = "Failed"; // => Queued → Failed (insufficient resources)
      throw new Error(`Insufficient memory. Required: ${job.memoryRequired}MB, Available: ${this.availableMemory}MB`); // => Method invocation
      // => Constructor creates new object instance
      // => Reject invalid operation
      // => Fail fast on FSM violation
    }

    this.availableMemory -= job.memoryRequired; // => Allocate memory
    this.currentJob = job; // => Assign job
    this.state = "Running"; // => Queued → Running (guard passed)
    console.log(`Job ${job.id} started, ${this.availableMemory}MB remaining`); // => Output for verification
    // => Debug/audit output
    // => Log for observability
  }

  completeJob(): void {
    // => Method invocation
    // => Begin object/config definition
    if (this.state !== "Running") {
      // => State-based guard condition
      // => Comparison check
      // => Comparison check
      // => Conditional check
      // => Branch execution based on condition
      throw new Error("No job running"); // => Method invocation
      // => Reject invalid operation
      // => Fail fast on FSM violation
    }

    this.availableMemory += this.currentJob!.memoryRequired; // => Release memory
    this.state = "Completed"; // => Running → Completed
    console.log(`Job ${this.currentJob!.id} completed, ${this.availableMemory}MB available`); // => Output for verification
    // => Debug/audit output
    // => Log for observability
  }
}

// Usage
const scheduler = new JobScheduler(); // => state: "Queued", memory: 1024MB

scheduler.startJob({ id: "job1", memoryRequired: 512 }); // => Queued → Running (512MB allocated)
// => FSM state management logic
console.log(scheduler.getCurrentState()); // => Output: Running
// => FSM state management logic
// => Pure read, no side effects

scheduler.completeJob(); // => Running → Completed (512MB released)
// => FSM state management logic
console.log(scheduler.getCurrentState()); // => Output: Completed
// => FSM state management logic
// => Pure read, no side effects

const scheduler2 = new JobScheduler(); // => New scheduler
try {
  // => Statement execution
  // => Begin error handling
  // => Begin error handling
  // => Begin object/config definition
  scheduler2.startJob({ id: "job2", memoryRequired: 2048 }); // => Guard fails (2048MB > 1024MB)
  // => FSM state management logic
} catch (error) {
  // => Method signature: defines function interface
  // => Catch errors
  // => Catch errors
  // => Begin object/config definition
  console.log(error.message); // => Output: Insufficient memory. Required: 2048MB, Available: 1024MB
  console.log(scheduler2.getCurrentState()); // => Output: Failed
  // => FSM state management logic
  // => Pure read, no side effects
}

Key Takeaway: Resource guards check availability (memory, connections, quotas) before transitions. Failed resource checks prevent transitions and may move to error states.

Why It Matters: Resource guards prevent cascading failures. When Kubernetes schedules pods, FSM guards check node resources (CPU, memory, disk) before transitioning pod to Running state. Failed guards keep pod in Pending state instead of starting and immediately crashing. Resource-aware FSMs reduce cluster instability by 80% compared to optimistic scheduling.

Example 17: Data Validation Guards

Guards validate data integrity before allowing transitions.

TypeScript Implementation:

// Payment processing FSM with validation guards
type PaymentState = "Pending" | "Validated" | "Processed" | "Rejected"; // => Four states
// => Type system ensures only valid states used

interface PaymentData {
  // => Type declaration defines structure
  // => Begin object/config definition
  amount: number; // => Payment amount
  cardNumber: string; // => Card number
  cvv: string; // => CVV code
}

class PaymentProcessor {
  // => Class encapsulates FSM logic
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: PaymentState = "Pending"; // => Initial: Pending
  // => FSM begins execution in Pending state
  private paymentData: PaymentData | null = null; // => Payment details
  // => Initialized alongside FSM state

  getCurrentState(): PaymentState {
    // => Accessor method definition
    // => Query method: read current FSM state
    // => Pure read, no side effects
    return this.state; // => Returns state
  }

  validatePayment(data: PaymentData): void {
    // => Method invocation
    // => Begin object/config definition
    if (this.state !== "Pending") {
      // => State-based guard condition
      // => Comparison check
      // => Comparison check
      // => Conditional check
      // => Branch execution based on condition
      throw new Error("Can only validate Pending payments"); // => Method invocation
      // => Reject invalid operation
      // => Fail fast on FSM violation
    }

    this.paymentData = data; // => Store payment data

    // Guard: amount validation
    if (data.amount <= 0) {
      // => Conditional branch
      // => Comparison check
      // => Comparison check
      // => Conditional check
      // => Branch execution based on condition
      // => Guard: positive amount?
      this.state = "Rejected"; // => Pending → Rejected (invalid amount)
      throw new Error("Amount must be positive"); // => Method invocation
      // => Reject invalid operation
      // => Fail fast on FSM violation
    }

    // Guard: card number validation (simple check)
    if (data.cardNumber.length !== 16) {
      // => Conditional branch
      // => Comparison check
      // => Comparison check
      // => Conditional check
      // => Branch execution based on condition
      // => Guard: 16-digit card?
      this.state = "Rejected"; // => Pending → Rejected (invalid card)
      throw new Error("Invalid card number length"); // => Method invocation
      // => Reject invalid operation
      // => Fail fast on FSM violation
    }

    // Guard: CVV validation
    if (data.cvv.length !== 3 && data.cvv.length !== 4) {
      // => Conditional branch
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Conditional check
      // => Branch execution based on condition
      // => Guard: valid CVV?
      this.state = "Rejected"; // => Pending → Rejected (invalid CVV)
      throw new Error("Invalid CVV length"); // => Method invocation
      // => Reject invalid operation
      // => Fail fast on FSM violation
    }

    this.state = "Validated"; // => Pending → Validated (all guards passed)
    console.log("Payment validated successfully"); // => Output for verification
    // => Debug/audit output
    // => Log for observability
  }

  processPayment(): void {
    // => Method invocation
    // => Begin object/config definition
    if (this.state !== "Validated") {
      // => State-based guard condition
      // => Comparison check
      // => Comparison check
      // => Conditional check
      // => Branch execution based on condition
      throw new Error("Can only process Validated payments"); // => Method invocation
      // => Reject invalid operation
      // => Fail fast on FSM violation
    }

    this.state = "Processed"; // => Validated → Processed
    console.log(`Processed payment of $${this.paymentData!.amount}`); // => Output for verification
    // => Debug/audit output
    // => Log for observability
  }
}

// Usage
const payment1 = new PaymentProcessor(); // => state: "Pending"
payment1.validatePayment({
  // => Statement execution
  // => Begin object/config definition
  // => All guards pass
  amount: 99.99, // => Statement execution
  cardNumber: "1234567890123456", // => Statement execution
  cvv: "123", // => Statement execution
}); // => Statement execution
console.log(payment1.getCurrentState()); // => Output: Validated
// => FSM state management logic
// => Pure read, no side effects
payment1.processPayment(); // => Validated → Processed
console.log(payment1.getCurrentState()); // => Output: Processed
// => FSM state management logic
// => Pure read, no side effects

const payment2 = new PaymentProcessor(); // => state: "Pending"
try {
  // => Statement execution
  // => Begin error handling
  // => Begin error handling
  // => Begin object/config definition
  payment2.validatePayment({
    // => Statement execution
    // => Begin object/config definition
    // => Guard fails (invalid amount)
    amount: -10, // => Statement execution
    cardNumber: "1234567890123456", // => Statement execution
    cvv: "123", // => Statement execution
  }); // => Statement execution
} catch (error) {
  // => Method signature: defines function interface
  // => Catch errors
  // => Catch errors
  // => Begin object/config definition
  console.log(error.message); // => Output: Amount must be positive
  console.log(payment2.getCurrentState()); // => Output: Rejected
  // => FSM state management logic
  // => Pure read, no side effects
}

Key Takeaway: Data validation guards check input data meets requirements (range checks, format validation, business rules). Multiple guards may run sequentially; first failure rejects transition.

Why It Matters: Validation guards prevent garbage-in-garbage-out. Payment systems experience failures from processing invalid data that should have been rejected earlier. Moving validation into FSM guards (before Pending → Validated transition) reduces processing costs and improves error messages because validation failures happen synchronously (immediate user feedback) instead of asynchronously (delayed notification).

Example 18: Composite Guards (AND/OR Logic)

Complex guards combine multiple conditions using boolean logic.

TypeScript Implementation:

// Loan approval FSM with composite guards
type LoanState = "Application" | "UnderReview" | "Approved" | "Rejected"; // => Four states
// => Type system ensures only valid states used

interface ApplicantData {
  // => Type declaration defines structure
  // => Begin object/config definition
  creditScore: number; // => Credit score (300-850)
  income: number; // => Annual income
  loanAmount: number; // => Requested loan amount
  hasCollateral: boolean; // => Has collateral?
  // => FSM state management logic
}

class LoanApproval {
  // => Class encapsulates FSM logic
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: LoanState = "Application"; // => Initial: Application
  // => FSM begins execution in Application state
  private applicantData: ApplicantData | null = null; // => Applicant data
  // => Initialized alongside FSM state

  getCurrentState(): LoanState {
    // => Accessor method definition
    // => Query method: read current FSM state
    // => Pure read, no side effects
    return this.state; // => Returns state
  }

  submitApplication(data: ApplicantData): void {
    // => Method invocation
    // => Begin object/config definition
    if (this.state !== "Application") {
      // => State-based guard condition
      // => Comparison check
      // => Comparison check
      // => Conditional check
      // => Branch execution based on condition
      throw new Error("Invalid state for submission"); // => Method invocation
      // => Reject invalid operation
      // => Fail fast on FSM violation
    }

    this.applicantData = data; // => Store applicant data
    this.state = "UnderReview"; // => Application → UnderReview
  }

  reviewApplication(): void {
    // => Method invocation
    // => Begin object/config definition
    if (this.state !== "UnderReview" || !this.applicantData) {
      // => State-based guard condition
      // => Logical OR: either condition can be true
      // => Comparison check
      // => Comparison check
      // => Conditional check
      // => Branch execution based on condition
      throw new Error("Invalid state for review"); // => Method invocation
      // => Reject invalid operation
      // => Fail fast on FSM violation
    }

    const data = this.applicantData; // => Get applicant data

    // Composite guard using AND/OR logic
    const hasGoodCredit = data.creditScore >= 700; // => Guard: credit >= 700
    const hasStableIncome = data.income >= 50000; // => Guard: income >= \$50k
    const reasonableDebtRatio = data.loanAmount / data.income <= 3; // => Guard: loan <= 3x income

    // Approval logic:
    // (Good credit AND stable income AND reasonable debt ratio) OR has collateral
    const approved = (hasGoodCredit && hasStableIncome && reasonableDebtRatio) || data.hasCollateral; // => Variable declaration and assignment
    // => Logical AND: both conditions must be true
    // => Assign value
    // => Assign value
    // => Initialize approved
    // => Composite guard: (A AND B AND C) OR D

    if (approved) {
      // => Conditional branch
      // => Conditional check
      // => Branch execution based on condition
      this.state = "Approved"; // => UnderReview → Approved
      console.log("Loan approved"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    } else {
      // => Statement execution
      // => Fallback branch
      this.state = "Rejected"; // => UnderReview → Rejected
      console.log("Loan rejected"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    }
  }
}

// Usage
const loan1 = new LoanApproval(); // => state: "Application"
loan1.submitApplication({
  // => Statement execution
  // => Begin object/config definition
  // => All AND guards pass
  creditScore: 750, // => Statement execution
  income: 80000, // => Statement execution
  loanAmount: 150000, // => Statement execution
  hasCollateral: false, // => Statement execution
}); // => Statement execution
loan1.reviewApplication(); // => UnderReview → Approved
console.log(loan1.getCurrentState()); // => Output: Approved
// => FSM state management logic
// => Pure read, no side effects

const loan2 = new LoanApproval(); // => state: "Application"
loan2.submitApplication({
  // => Statement execution
  // => Begin object/config definition
  // => AND guards fail, but OR passes
  creditScore: 600, // => Statement execution
  income: 40000, // => Statement execution
  loanAmount: 200000, // => Statement execution
  hasCollateral: true, // => Collateral enables approval
}); // => Statement execution
loan2.reviewApplication(); // => UnderReview → Approved (via collateral)
// => FSM state management logic
console.log(loan2.getCurrentState()); // => Output: Approved
// => FSM state management logic
// => Pure read, no side effects

const loan3 = new LoanApproval(); // => state: "Application"
loan3.submitApplication({
  // => Statement execution
  // => Begin object/config definition
  // => Both AND and OR fail
  creditScore: 600, // => Statement execution
  income: 40000, // => Statement execution
  loanAmount: 200000, // => Statement execution
  hasCollateral: false, // => Statement execution
}); // => Statement execution
loan3.reviewApplication(); // => UnderReview → Rejected
console.log(loan3.getCurrentState()); // => Output: Rejected
// => FSM state management logic
// => Pure read, no side effects

Key Takeaway: Composite guards combine multiple conditions using AND/OR logic. Complex business rules (credit AND income AND debt ratio) OR (collateral) modeled as boolean expressions. All guard conditions must evaluate before transition.

Why It Matters: Real business rules are complex. When LendingClub implemented loan approval FSMs, composite guards captured actual underwriting rules (30+ conditions combined with AND/OR/NOT logic). Explicit guard logic reduced loan default rate by 15% because business rules were visible and testable. Analysts could review FSM guard logic directly instead of reverse-engineering from code.

Entry and Exit Actions (Examples 19-23)

Example 19: State Entry Actions

Entry actions execute when entering a state, regardless of which transition brought you there.

TypeScript Implementation:

// Game character FSM with entry actions
type CharacterState = "Idle" | "Walking" | "Running" | "Jumping"; // => Four states
// => Type system ensures only valid states used
type CharacterEvent = "walk" | "run" | "jump" | "land" | "stop"; // => Five events
// => Events trigger state transitions

class GameCharacter {
  // => Class encapsulates FSM logic
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: CharacterState = "Idle"; // => Initial: Idle
  // => FSM begins execution in Idle state

  getCurrentState(): CharacterState {
    // => Accessor method definition
    // => Query method: read current FSM state
    // => Pure read, no side effects
    return this.state; // => Returns state
  }

  handleEvent(event: CharacterEvent): void {
    // => Event handler method
    // => Event handler: main FSM dispatch method
    // => Processes events, triggers transitions
    const previousState = this.state; // => Store previous state

    // Transition logic
    if (this.state === "Idle" && event === "walk") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Event type check
      // => Combined (state, event) guard
      this.state = "Walking"; // => Idle → Walking
    } else if (this.state === "Idle" && event === "run") {
      // => Method signature: defines function interface
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.state = "Running"; // => Idle → Running
    } else if (this.state === "Walking" && event === "run") {
      // => Method signature: defines function interface
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.state = "Running"; // => Walking → Running
    } else if (this.state === "Running" && event === "jump") {
      // => Method signature: defines function interface
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.state = "Jumping"; // => Running → Jumping
    } else if (this.state === "Jumping" && event === "land") {
      // => Method signature: defines function interface
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.state = "Idle"; // => Jumping → Idle
    } else if (event === "stop") {
      // => Method signature: defines function interface
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.state = "Idle"; // => Any → Idle (stop button)
    }

    // Entry actions (execute when entering new state)
    if (this.state !== previousState) {
      // => State-based guard condition
      // => Comparison check
      // => Comparison check
      // => Conditional check
      // => Branch execution based on condition
      // => State changed?
      this.onEnter(this.state); // => Execute entry action
    }
  }

  private onEnter(state: CharacterState): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    if (state === "Walking") {
      // => State-based guard condition
      // => Comparison check
      // => Comparison check
      // => Conditional check
      // => Branch execution based on condition
      console.log("▶ Entry action: Start walking animation"); // => Entry action for Walking
      // => Log for observability
      this.setAnimationSpeed(1.0); // => Normal speed
    } else if (state === "Running") {
      // => Method signature: defines function interface
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      console.log("▶ Entry action: Start running animation"); // => Entry action for Running
      // => Log for observability
      this.setAnimationSpeed(2.0); // => Double speed
    } else if (state === "Jumping") {
      // => Method signature: defines function interface
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      console.log("▶ Entry action: Play jump sound"); // => Entry action for Jumping
      // => Log for observability
      this.playSound("jump.wav"); // => Trigger sound effect
    } else if (state === "Idle") {
      // => Method signature: defines function interface
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      console.log("▶ Entry action: Start idle animation"); // => Entry action for Idle
      // => Log for observability
      this.setAnimationSpeed(0.5); // => Slow idle breathing
    }
    // => Entry actions run regardless of which transition brought us here
  }

  private setAnimationSpeed(speed: number): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log(`  Set animation speed: ${speed}x`); // => Mock animation API
    // => FSM state management logic
    // => Log for observability
  }

  private playSound(file: string): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log(`  Play sound: ${file}`); // => Mock audio API
    // => FSM state management logic
    // => Log for observability
  }
}

// Usage
const character = new GameCharacter(); // => state: "Idle"

character.handleEvent("walk"); // => Idle → Walking
// => Processes events, triggers transitions
// => Output:
// ▶ Entry action: Start walking animation
//   Set animation speed: 1.0x

character.handleEvent("run"); // => Walking → Running
// => Processes events, triggers transitions
// => Output:
// ▶ Entry action: Start running animation
//   Set animation speed: 2.0x

character.handleEvent("jump"); // => Running → Jumping
// => Processes events, triggers transitions
// => Output:
// ▶ Entry action: Play jump sound
//   Play sound: jump.wav

character.handleEvent("land"); // => Jumping → Idle
// => Processes events, triggers transitions
// => Output:
// ▶ Entry action: Start idle animation
//   Set animation speed: 0.5x

Key Takeaway: Entry actions execute automatically when entering state, regardless of source state. Centralizes setup logic (start animations, play sounds) that must happen for every transition into that state.

Why It Matters: Entry actions eliminate code duplication. When Unity game developers analyzed character controller code, they found animation setup duplicated across 10+ transition handlers (Idle→Walking, Running→Walking, Jumping→Walking all called startWalkingAnimation()). Consolidating into Walking state entry action reduced code by 70% and fixed 15 bugs where some transitions forgot to call setup logic.

Example 20: State Exit Actions

Exit actions execute when leaving a state, regardless of which transition takes you out.

TypeScript Implementation:

// Database connection FSM with exit actions
type ConnectionState = "Disconnected" | "Connecting" | "Connected" | "Error"; // => Four states
// => Type system ensures only valid states used
type ConnectionEvent = "connect" | "connection_success" | "connection_failed" | "disconnect"; // => Four events
// => Events trigger state transitions

class DatabaseConnection {
  // => Class encapsulates FSM logic
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: ConnectionState = "Disconnected"; // => Initial: Disconnected
  // => FSM begins execution in Disconnected state
  private connectionHandle: string | null = null; // => Mock connection handle
  // => Initialized alongside FSM state

  getCurrentState(): ConnectionState {
    // => Accessor method definition
    // => Query method: read current FSM state
    // => Pure read, no side effects
    return this.state; // => Returns state
  }

  handleEvent(event: ConnectionEvent): void {
    // => Event handler method
    // => Event handler: main FSM dispatch method
    // => Processes events, triggers transitions
    const previousState = this.state; // => Store previous state

    // Transition logic
    if (this.state === "Disconnected" && event === "connect") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Event type check
      // => Combined (state, event) guard
      this.state = "Connecting"; // => Disconnected → Connecting
    } else if (this.state === "Connecting" && event === "connection_success") {
      // => Method signature: defines function interface
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.state = "Connected"; // => Connecting → Connected
    } else if (this.state === "Connecting" && event === "connection_failed") {
      // => Method signature: defines function interface
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.state = "Error"; // => Connecting → Error
    } else if (this.state === "Connected" && event === "disconnect") {
      // => Method signature: defines function interface
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.state = "Disconnected"; // => Connected → Disconnected
    }

    // Exit actions (execute when leaving state)
    if (this.state !== previousState) {
      // => State-based guard condition
      // => Comparison check
      // => Comparison check
      // => Conditional check
      // => Branch execution based on condition
      // => State changed?
      this.onExit(previousState); // => Execute exit action
      this.onEnter(this.state); // => Then entry action
    }
  }

  private onExit(state: ConnectionState): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    if (state === "Connected") {
      // => State-based guard condition
      // => Comparison check
      // => Comparison check
      // => Conditional check
      // => Branch execution based on condition
      console.log("◀ Exit action: Close database connection"); // => Exit action for Connected
      // => Log for observability
      this.closeConnection(); // => Cleanup resources
    } else if (state === "Connecting") {
      // => Method signature: defines function interface
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      console.log("◀ Exit action: Cancel connection attempt"); // => Exit action for Connecting
      // => Log for observability
      this.cancelConnection(); // => Abort in-progress connection
    }
    // => Exit actions run regardless of which transition takes us out
  }

  private onEnter(state: ConnectionState): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    if (state === "Connecting") {
      // => State-based guard condition
      // => Comparison check
      // => Comparison check
      // => Conditional check
      // => Branch execution based on condition
      console.log("▶ Entry action: Open connection"); // => Entry action for Connecting
      // => Log for observability
      this.connectionHandle = "conn-" + Date.now(); // => Mock connection handle
    } else if (state === "Connected") {
      // => Method signature: defines function interface
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      console.log("▶ Entry action: Connection established"); // => Entry action for Connected
      // => Log for observability
    }
  }

  private closeConnection(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log(`  Closing connection: ${this.connectionHandle}`); // => Mock close
    // => FSM state management logic
    // => Log for observability
    this.connectionHandle = null; // => Release handle
  }

  private cancelConnection(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log(`  Cancelling connection: ${this.connectionHandle}`); // => Mock cancel
    // => FSM state management logic
    // => Log for observability
    this.connectionHandle = null; // => Release handle
  }
}

// Usage
const db = new DatabaseConnection(); // => state: "Disconnected"

db.handleEvent("connect"); // => Disconnected → Connecting
// => Processes events, triggers transitions
// => Output:
// ▶ Entry action: Open connection
//   Opening connection: conn-1234567890

db.handleEvent("connection_success"); // => Connecting → Connected
// => Processes events, triggers transitions
// => Output:
// ◀ Exit action: Cancel connection attempt (even though success)
//   Cancelling connection: conn-1234567890
// ▶ Entry action: Connection established

db.handleEvent("disconnect"); // => Connected → Disconnected
// => Processes events, triggers transitions
// => Output:
// ◀ Exit action: Close database connection
//   Closing connection: conn-1234567890

Key Takeaway: Exit actions execute automatically when leaving state, before entering next state. Centralizes cleanup logic (close connections, release resources) that must happen for every transition out of that state.

Why It Matters: Exit actions prevent resource leaks. Desktop apps experience memory leaks when WebSocket connections aren’t closed when switching channels because each transition handler must remember to call cleanup methods. Moving cleanup to exit actions (leave ChannelA state → close socket) guarantees cleanup regardless of where you’re going, reducing memory leaks significantly.

Example 21: Entry and Exit Actions Together

Most FSMs use both entry and exit actions for complete state lifecycle management.

TypeScript Implementation:

// HTTP request FSM with entry/exit actions
type RequestState = "Idle" | "Pending" | "Success" | "Failed"; // => Four states
// => Type system ensures only valid states used
type RequestEvent = "send" | "response_ok" | "response_error" | "timeout"; // => Four events
// => Events trigger state transitions

class HttpRequest {
  // => Class encapsulates FSM logic
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: RequestState = "Idle"; // => Initial: Idle
  // => FSM begins execution in Idle state
  private timeoutHandle: NodeJS.Timeout | null = null; // => Timeout timer
  // => Initialized alongside FSM state
  private requestStartTime: Date | null = null; // => Track request timing
  // => Initialized alongside FSM state

  getCurrentState(): RequestState {
    // => Accessor method definition
    // => Query method: read current FSM state
    // => Pure read, no side effects
    return this.state; // => Returns state
  }

  handleEvent(event: RequestEvent): void {
    // => Event handler method
    // => Event handler: main FSM dispatch method
    // => Processes events, triggers transitions
    const previousState = this.state; // => Store previous state

    // Transition logic
    if (this.state === "Idle" && event === "send") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Event type check
      // => Combined (state, event) guard
      this.state = "Pending"; // => Idle → Pending
    } else if (this.state === "Pending" && event === "response_ok") {
      // => Method signature: defines function interface
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.state = "Success"; // => Pending → Success
    } else if (this.state === "Pending" && event === "response_error") {
      // => Method signature: defines function interface
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.state = "Failed"; // => Pending → Failed
    } else if (this.state === "Pending" && event === "timeout") {
      // => Method signature: defines function interface
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.state = "Failed"; // => Pending → Failed (timeout)
    }

    // Execute exit then entry actions
    if (this.state !== previousState) {
      // => State-based guard condition
      // => Comparison check
      // => Comparison check
      // => Conditional check
      // => Branch execution based on condition
      this.onExit(previousState); // => Exit action first
      this.onEnter(this.state); // => Entry action second
    }
  }

  private onEnter(state: RequestState): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    if (state === "Pending") {
      // => State-based guard condition
      // => Comparison check
      // => Comparison check
      // => Conditional check
      // => Branch execution based on condition
      console.log("▶ Entry: Start request timer"); // => Entry action for Pending
      // => Log for observability
      this.requestStartTime = new Date(); // => Record start time
      // => FSM state management logic
      this.timeoutHandle = setTimeout(() => {
        // => Event handler method
        // => Chained method calls or nested operations
        // => Assign value
        // => Assign value
        // => Begin object/config definition
        // => Start timeout timer
        this.handleEvent("timeout"); // => Event handler method
        // => Event handler: main FSM dispatch method
        // => Processes events, triggers transitions
      }, 5000); // => Statement execution
    } else if (state === "Success") {
      // => Method signature: defines function interface
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      const duration = Date.now() - this.requestStartTime!.getTime(); // => Calculate duration
      // => FSM state management logic
      console.log(`▶ Entry: Request succeeded (${duration}ms)`); // => Entry action for Success
      // => FSM state management logic
      // => Log for observability
    } else if (state === "Failed") {
      // => Method signature: defines function interface
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      console.log("▶ Entry: Request failed"); // => Entry action for Failed
      // => Log for observability
    }
  }

  private onExit(state: RequestState): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    if (state === "Pending") {
      // => State-based guard condition
      // => Comparison check
      // => Comparison check
      // => Conditional check
      // => Branch execution based on condition
      console.log("◀ Exit: Clear request timer"); // => Exit action for Pending
      // => Log for observability
      if (this.timeoutHandle) {
        // => Event handler method
        // => Conditional check
        // => Branch execution based on condition
        clearTimeout(this.timeoutHandle); // => Cancel timeout timer
        this.timeoutHandle = null; // => Clear handle
      }
    }
    // => Exit action ensures timer cleanup regardless of outcome
  }
}

// Usage (simulated)
const request = new HttpRequest(); // => state: "Idle"

request.handleEvent("send"); // => Idle → Pending
// => Processes events, triggers transitions
// => Output:
// ▶ Entry: Start request timer

// Simulate successful response
request.handleEvent("response_ok"); // => Pending → Success
// => Processes events, triggers transitions
// => Output:
// ◀ Exit: Clear request timer
// ▶ Entry: Request succeeded (150ms)

Key Takeaway: Entry actions setup state resources (start timers, open connections); exit actions cleanup state resources (clear timers, close connections). Together they ensure complete state lifecycle management.

Why It Matters: Lifecycle management prevents bugs and resource leaks. Video conferencing systems experience connectivity issues from connection timers not being cleared when state changed, leading to spurious timeout errors. Entry/exit action pattern (entry: start timer, exit: clear timer) guarantees cleanup, significantly reducing false timeout errors.

Example 22: Entry/Exit Actions vs Transition Actions

Entry/exit actions run for ANY transition into/out of state. Transition actions run for SPECIFIC transitions only.

TypeScript Implementation (showing both):

// Authentication FSM comparing entry/exit vs transition actions
type AuthState = "LoggedOut" | "LoggingIn" | "LoggedIn"; // => Three states
// => Type system ensures only valid states used
type AuthEvent = "login" | "login_success" | "logout"; // => Three events
// => Events trigger state transitions

class AuthWithActions {
  // => Class encapsulates FSM logic
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: AuthState = "LoggedOut"; // => Initial: LoggedOut
  // => FSM begins execution in LoggedOut state

  getCurrentState(): AuthState {
    // => Accessor method definition
    // => Query method: read current FSM state
    // => Pure read, no side effects
    return this.state; // => Returns state
  }

  handleEvent(event: AuthEvent): void {
    // => Event handler method
    // => Event handler: main FSM dispatch method
    // => Processes events, triggers transitions
    const previousState = this.state; // => Store previous state

    // Transition logic with transition-specific actions
    if (this.state === "LoggedOut" && event === "login") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Event type check
      // => Combined (state, event) guard
      this.onTransition_LoggedOut_to_LoggingIn(); // => Transition action (specific)
      // => FSM state management logic
      this.state = "LoggingIn"; // => Change state
    } else if (this.state === "LoggingIn" && event === "login_success") {
      // => Method signature: defines function interface
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.state = "LoggedIn"; // => Change state
    } else if (this.state === "LoggedIn" && event === "logout") {
      // => Method signature: defines function interface
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.state = "LoggedOut"; // => Change state
    }

    // Execute exit/entry actions (run for ANY transition)
    if (this.state !== previousState) {
      // => State-based guard condition
      // => Comparison check
      // => Comparison check
      // => Conditional check
      // => Branch execution based on condition
      this.onExit(previousState); // => Exit action (generic)
      // => FSM state management logic
      this.onEnter(this.state); // => Entry action (generic)
      // => FSM state management logic
    }
  }

  // ENTRY ACTIONS (run for ANY transition INTO this state)
  private onEnter(state: AuthState): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    if (state === "LoggingIn") {
      // => State-based guard condition
      // => Comparison check
      // => Comparison check
      // => Conditional check
      // => Branch execution based on condition
      console.log("▶ Entry: Show loading spinner"); // => Runs for ANY transition into LoggingIn
      // => Log for observability
    } else if (state === "LoggedIn") {
      // => Method signature: defines function interface
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      console.log("▶ Entry: Load user dashboard"); // => Runs for ANY transition into LoggedIn
      // => Log for observability
    }
  }

  // EXIT ACTIONS (run for ANY transition OUT OF this state)
  private onExit(state: AuthState): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    if (state === "LoggingIn") {
      // => State-based guard condition
      // => Comparison check
      // => Comparison check
      // => Conditional check
      // => Branch execution based on condition
      console.log("◀ Exit: Hide loading spinner"); // => Runs for ANY transition out of LoggingIn
      // => Log for observability
    }
  }

  // TRANSITION ACTIONS (run for SPECIFIC transition only)
  private onTransition_LoggedOut_to_LoggingIn(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("→ Transition: Validate credentials"); // => Only for LoggedOut → LoggingIn
    // => Log for observability
    console.log("→ Transition: Send API request"); // => Specific to this transition
    // => Log for observability
  }
}

// Usage
const auth = new AuthWithActions(); // => state: "LoggedOut"

auth.handleEvent("login"); // => LoggedOut → LoggingIn
// => Processes events, triggers transitions
// => Output:
// → Transition: Validate credentials (transition action)
// → Transition: Send API request (transition action)
// ▶ Entry: Show loading spinner (entry action)

auth.handleEvent("login_success"); // => LoggingIn → LoggedIn
// => Processes events, triggers transitions
// => Output:
// ◀ Exit: Hide loading spinner (exit action)
// ▶ Entry: Load user dashboard (entry action)

Key Takeaway: Entry/exit actions run for every transition into/out of state (generic setup/cleanup). Transition actions run for specific transitions only (specialized logic). Use entry/exit for common behavior, transition actions for unique behavior.

Why It Matters: Choosing correct action type prevents code duplication or scattered logic. When Twitter migrated to React, they analyzed state management and found 60% of component lifecycle methods (componentDidMount, componentWillUnmount) were duplicating logic that should be entry/exit actions. Proper separation (entry: start timer, exit: stop timer, transition: send specific analytics event) reduced React component code by 40%.

Example 23: Nested Entry/Exit Actions

Entry/exit actions can call other methods, enabling rich state lifecycle behaviors.

TypeScript Implementation:

// Video streaming FSM with nested entry/exit actions
type VideoState = "Stopped" | "Buffering" | "Playing" | "Paused"; // => Four states
// => Type system ensures only valid states used
type VideoEvent = "play" | "buffer_complete" | "pause" | "stop"; // => Four events
// => Events trigger state transitions

class VideoPlayer {
  // => Class encapsulates FSM logic
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: VideoState = "Stopped"; // => Initial: Stopped
  // => FSM begins execution in Stopped state
  private bufferLevel = 0; // => Buffer percentage
  // => Initialized alongside FSM state

  getCurrentState(): VideoState {
    // => Accessor method definition
    // => Query method: read current FSM state
    // => Pure read, no side effects
    return this.state; // => Returns state
  }

  handleEvent(event: VideoEvent): void {
    // => Event handler method
    // => Event handler: main FSM dispatch method
    // => Processes events, triggers transitions
    const previousState = this.state; // => Store previous state

    // Transition logic
    if (event === "play" && this.state === "Stopped") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Event type check
      // => Combined (state, event) guard
      this.state = "Buffering"; // => Stopped → Buffering
    } else if (event === "buffer_complete" && this.state === "Buffering") {
      // => Method signature: defines function interface
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.state = "Playing"; // => Buffering → Playing
    } else if (event === "pause" && this.state === "Playing") {
      // => Method signature: defines function interface
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.state = "Paused"; // => Playing → Paused
    } else if (event === "play" && this.state === "Paused") {
      // => Method signature: defines function interface
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.state = "Playing"; // => Paused → Playing
    } else if (event === "stop") {
      // => Method signature: defines function interface
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.state = "Stopped"; // => Any → Stopped
    }

    // Execute lifecycle actions
    if (this.state !== previousState) {
      // => State-based guard condition
      // => Comparison check
      // => Comparison check
      // => Conditional check
      // => Branch execution based on condition
      this.onExit(previousState); // => Exit previous state
      this.onEnter(this.state); // => Enter new state
      // => FSM state management logic
    }
  }

  private onEnter(state: VideoState): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    if (state === "Buffering") {
      // => State-based guard condition
      // => Comparison check
      // => Comparison check
      // => Conditional check
      // => Branch execution based on condition
      console.log("▶ Entry: Start buffering"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      this.startBuffering(); // => Nested call: start buffer
      this.showBufferingIndicator(); // => Nested call: show UI
    } else if (state === "Playing") {
      // => Method signature: defines function interface
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      console.log("▶ Entry: Start playback"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      this.resumePlayback(); // => Nested call: play video
      this.hideBufferingIndicator(); // => Nested call: hide UI
      this.trackAnalytics("play"); // => Nested call: analytics
    } else if (state === "Paused") {
      // => Method signature: defines function interface
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      console.log("▶ Entry: Pause playback"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      this.pausePlayback(); // => Nested call: pause
    } else if (state === "Stopped") {
      // => Method signature: defines function interface
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      console.log("▶ Entry: Stop playback"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      this.stopPlayback(); // => Nested call: stop
      this.clearBuffer(); // => Nested call: cleanup buffer
    }
  }

  private onExit(state: VideoState): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    if (state === "Buffering") {
      // => State-based guard condition
      // => Comparison check
      // => Comparison check
      // => Conditional check
      // => Branch execution based on condition
      console.log("◀ Exit: Buffering complete"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      this.trackAnalytics("buffer_complete"); // => Nested call: analytics
    }
  }

  // Nested helper methods called by entry/exit actions
  private startBuffering(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("  → Downloading video data"); // => Nested action
    // => Log for observability
    this.bufferLevel = 50; // => Mock buffer
  }

  private showBufferingIndicator(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("  → Display buffering spinner"); // => Nested action
    // => Log for observability
  }

  private hideBufferingIndicator(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("  → Hide buffering spinner"); // => Nested action
    // => Log for observability
  }

  private resumePlayback(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("  → Resume video playback"); // => Nested action
    // => Log for observability
  }

  private pausePlayback(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("  → Pause video"); // => Nested action
    // => Log for observability
  }

  private stopPlayback(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("  → Stop video"); // => Nested action
    // => Log for observability
  }

  private clearBuffer(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("  → Clear buffer memory"); // => Nested action
    // => Log for observability
    this.bufferLevel = 0; // => Statement execution
    // => Access instance property
    // => Access instance property
  }

  private trackAnalytics(event: string): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log(`  → Track analytics: ${event}`); // => Nested action
    // => FSM state management logic
    // => Log for observability
  }
}

// Usage
const video = new VideoPlayer(); // => state: "Stopped"

video.handleEvent("play"); // => Stopped → Buffering
// => Processes events, triggers transitions
// => Output:
// ▶ Entry: Start buffering
//   → Downloading video data
//   → Display buffering spinner

video.handleEvent("buffer_complete"); // => Buffering → Playing
// => Processes events, triggers transitions
// => Output:
// ◀ Exit: Buffering complete
//   → Track analytics: buffer_complete
// ▶ Entry: Start playback
//   → Resume video playback
//   → Hide buffering spinner
//   → Track analytics: play

Key Takeaway: Entry/exit actions can orchestrate multiple operations by calling helper methods. This enables rich state lifecycle behavior (UI updates, analytics, resource management) without cluttering transition logic.

Why It Matters: Nested actions organize complex state behavior. When YouTube analyzed video player bugs, they found state transition code mixed UI updates, analytics, playback control, and buffer management—making bugs hard to trace. Refactoring to nested entry/exit actions (entry: call setup helpers, exit: call cleanup helpers) reduced state-related bugs by 65% because each concern was isolated and testable.

Transition Actions (Examples 24-27)

Example 24: Transition-Specific Actions

Transition actions execute for specific state transitions, carrying transition-specific logic.

TypeScript Implementation:

// Shopping cart FSM with transition actions
type CartState = "Empty" | "HasItems" | "CheckingOut" | "OrderPlaced"; // => Four states
// => Type system ensures only valid states used
type CartEvent = "add_item" | "checkout" | "payment_success"; // => Three events
// => Events trigger state transitions

interface Item {
  // => Type declaration defines structure
  // => Begin object/config definition
  id: string; // => Item ID
  price: number; // => Item price
}

class ShoppingCart {
  // => Class encapsulates FSM logic
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: CartState = "Empty"; // => Initial: Empty
  // => FSM begins execution in Empty state
  private items: Item[] = []; // => Cart items
  // => Initialized alongside FSM state
  private totalAmount = 0; // => Cart total
  // => Initialized alongside FSM state

  getCurrentState(): CartState {
    // => Accessor method definition
    // => Query method: read current FSM state
    // => Pure read, no side effects
    return this.state; // => Returns state
  }

  handleEvent(event: CartEvent, data?: any): void {
    // => Event handler method
    // => Ternary: condition ? true_branch : false_branch
    // => Event handler: main FSM dispatch method
    // => Processes events, triggers transitions
    if (event === "add_item" && this.state === "Empty") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Event type check
      // => Combined (state, event) guard
      this.onTransition_AddFirstItem(data); // => Transition action (Empty → HasItems)
      // => FSM state management logic
      this.state = "HasItems"; // => Empty → HasItems
    } else if (event === "add_item" && this.state === "HasItems") {
      // => Method signature: defines function interface
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.onTransition_AddAnotherItem(data); // => Transition action (HasItems → HasItems)
      // => FSM state management logic
      // => State stays HasItems (self-transition)
    } else if (event === "checkout" && this.state === "HasItems") {
      // => Method signature: defines function interface
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.onTransition_StartCheckout(); // => Transition action (HasItems → CheckingOut)
      // => FSM state management logic
      this.state = "CheckingOut"; // => HasItems → CheckingOut
    } else if (event === "payment_success" && this.state === "CheckingOut") {
      // => Method signature: defines function interface
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.onTransition_CompleteOrder(); // => Transition action (CheckingOut → OrderPlaced)
      // => FSM state management logic
      this.state = "OrderPlaced"; // => CheckingOut → OrderPlaced
    }
  }

  // TRANSITION ACTION: Empty → HasItems (first item)
  private onTransition_AddFirstItem(item: Item): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("→ Transition: Add first item"); // => Output for verification
    // => Debug/audit output
    // => Log for observability
    this.items.push(item); // => Add item to cart
    this.totalAmount = item.price; // => Set total
    console.log(`  Cart created with ${item.id} ($${item.price})`); // => Output for verification
    // => Chained method calls or nested operations
    // => Debug/audit output
    // => Log for observability
    this.trackAnalytics("cart_started"); // => Analytics for first item
  }

  // TRANSITION ACTION: HasItems → HasItems (additional items)
  private onTransition_AddAnotherItem(item: Item): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("→ Transition: Add item to existing cart"); // => Output for verification
    // => Debug/audit output
    // => Log for observability
    this.items.push(item); // => Add item
    this.totalAmount += item.price; // => Update total
    console.log(`  Added ${item.id}, new total: $${this.totalAmount}`); // => Output for verification
    // => Constructor creates new object instance
    // => Debug/audit output
    // => Log for observability
  }

  // TRANSITION ACTION: HasItems → CheckingOut
  private onTransition_StartCheckout(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("→ Transition: Start checkout process"); // => Output for verification
    // => Debug/audit output
    // => Log for observability
    console.log(`  Cart total: $${this.totalAmount} (${this.items.length} items)`); // => Output for verification
    // => Chained method calls or nested operations
    // => Debug/audit output
    // => Log for observability
    this.validateInventory(); // => Check item availability
    this.calculateTax(); // => Add tax calculation
  }

  // TRANSITION ACTION: CheckingOut → OrderPlaced
  private onTransition_CompleteOrder(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("→ Transition: Complete order"); // => Output for verification
    // => Debug/audit output
    // => Log for observability
    this.generateOrderId(); // => Create order ID
    this.sendConfirmationEmail(); // => Email customer
    this.updateInventory(); // => Reduce stock
    this.trackAnalytics("purchase_complete"); // => Analytics
  }

  private validateInventory(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("  → Validate item availability"); // => Mock validation
    // => Log for observability
  }

  private calculateTax(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("  → Calculate tax"); // => Mock tax calc
    // => Log for observability
  }

  private generateOrderId(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("  → Order ID: ORD-12345"); // => Mock order ID
    // => Log for observability
  }

  private sendConfirmationEmail(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("  → Send confirmation email"); // => Mock email
    // => Log for observability
  }

  private updateInventory(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("  → Update inventory"); // => Mock inventory update
    // => Log for observability
  }

  private trackAnalytics(event: string): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log(`  → Analytics: ${event}`); // => Mock analytics
    // => FSM state management logic
    // => Log for observability
  }
}

// Usage
const cart = new ShoppingCart(); // => state: "Empty"

cart.handleEvent("add_item", { id: "laptop", price: 1200 }); // => Empty → HasItems
// => FSM state management logic
// => Processes events, triggers transitions
// => Output:
// → Transition: Add first item
//   Cart created with laptop (\$1200)
//   → Analytics: cart_started

cart.handleEvent("add_item", { id: "mouse", price: 25 }); // => HasItems → HasItems
// => FSM state management logic
// => Processes events, triggers transitions
// => Output:
// → Transition: Add item to existing cart
//   Added mouse, new total: \$1225

cart.handleEvent("checkout"); // => HasItems → CheckingOut
// => Processes events, triggers transitions
// => Output:
// → Transition: Start checkout process
//   Cart total: \$1225 (2 items)
//   → Validate item availability
//   → Calculate tax

cart.handleEvent("payment_success"); // => CheckingOut → OrderPlaced
// => Processes events, triggers transitions
// => Output:
// → Transition: Complete order
//   → Order ID: ORD-12345
//   → Send confirmation email
//   → Update inventory
//   → Analytics: purchase_complete

Key Takeaway: Transition actions execute logic specific to particular state transitions. First item (Empty → HasItems) triggers different action than additional items (HasItems → HasItems self-transition). Each transition has unique responsibilities.

Why It Matters: Transition-specific logic captures business rules. Checkout systems use transition actions (HasItems → CheckingOut) to validate inventory, calculate shipping, apply coupons—logic that only makes sense during that specific transition, not as entry/exit actions. Proper transition action placement reduces checkout abandonment by moving slow operations to appropriate transitions.

Example 25: Transition Actions with Side Effects

Transition actions often trigger external side effects (API calls, database updates, notifications).

TypeScript Implementation:

// Order fulfillment FSM with side effects
type OrderState = "Pending" | "Confirmed" | "Shipped" | "Delivered"; // => Four states
// => Type system ensures only valid states used
type OrderEvent = "confirm" | "ship" | "deliver"; // => Three events
// => Events trigger state transitions

interface Order {
  // => Type declaration defines structure
  // => Begin object/config definition
  orderId: string; // => Order ID
  customerId: string; // => Customer ID
  trackingNumber?: string; // => Tracking number (after ship)
  // => FSM state management logic
}

class OrderFulfillment {
  // => Class encapsulates FSM logic
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: OrderState = "Pending"; // => Initial: Pending
  // => FSM begins execution in Pending state
  private order: Order; // => Order data
  // => Initialized alongside FSM state

  constructor(order: Order) {
    // => Method invocation
    // => Begin object/config definition
    this.order = order; // => Store order
  }

  getCurrentState(): OrderState {
    // => Accessor method definition
    // => Query method: read current FSM state
    // => Pure read, no side effects
    return this.state; // => Returns state
  }

  handleEvent(event: OrderEvent): void {
    // => Event handler method
    // => Event handler: main FSM dispatch method
    // => Processes events, triggers transitions
    if (event === "confirm" && this.state === "Pending") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Event type check
      // => Combined (state, event) guard
      this.onTransition_ConfirmOrder(); // => Transition action with side effects
      this.state = "Confirmed"; // => Pending → Confirmed
    } else if (event === "ship" && this.state === "Confirmed") {
      // => Method signature: defines function interface
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.onTransition_ShipOrder(); // => Transition action with side effects
      this.state = "Shipped"; // => Confirmed → Shipped
    } else if (event === "deliver" && this.state === "Shipped") {
      // => Method signature: defines function interface
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.onTransition_DeliverOrder(); // => Transition action with side effects
      this.state = "Delivered"; // => Shipped → Delivered
    }
  }

  // TRANSITION ACTION: Pending → Confirmed (multiple side effects)
  private onTransition_ConfirmOrder(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("→ Transition: Confirm order"); // => Output for verification
    // => Debug/audit output
    // => Log for observability
    this.chargePayment(); // => Side effect: payment
    this.sendEmailNotification("confirmation"); // => Side effect: email
    this.createWarehouseTicket(); // => Side effect: warehouse system
    this.updateInventoryReservation(); // => Side effect: inventory
    console.log(`  Order ${this.order.orderId} confirmed`); // => Output for verification
    // => Debug/audit output
    // => Log for observability
  }

  // TRANSITION ACTION: Confirmed → Shipped (multiple side effects)
  private onTransition_ShipOrder(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("→ Transition: Ship order"); // => Output for verification
    // => Debug/audit output
    // => Log for observability
    this.order.trackingNumber = this.generateTrackingNumber(); // => Generate tracking
    this.updateInventoryActual(); // => Side effect: decrement stock
    this.sendEmailNotification("shipped"); // => Side effect: email
    this.notifyDeliveryPartner(); // => Side effect: shipping API
    console.log(`  Order ${this.order.orderId} shipped (tracking: ${this.order.trackingNumber})`); // => Output for verification
    // => Chained method calls or nested operations
    // => Debug/audit output
    // => Log for observability
  }

  // TRANSITION ACTION: Shipped → Delivered (multiple side effects)
  private onTransition_DeliverOrder(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("→ Transition: Deliver order"); // => Output for verification
    // => Debug/audit output
    // => Log for observability
    this.sendEmailNotification("delivered"); // => Side effect: email
    this.updateCustomerLoyaltyPoints(); // => Side effect: loyalty system
    this.closeWarehouseTicket(); // => Side effect: warehouse system
    this.archiveOrder(); // => Side effect: database
    console.log(`  Order ${this.order.orderId} delivered`); // => Output for verification
    // => Debug/audit output
    // => Log for observability
  }

  // Side effect methods (would call external APIs in real implementation)
  private chargePayment(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("  → [API] Charge payment"); // => Payment gateway API
    // => Log for observability
  }

  private sendEmailNotification(type: string): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log(`  → [API] Send ${type} email to ${this.order.customerId}`); // => Email service API
    // => FSM state management logic
    // => Log for observability
  }

  private createWarehouseTicket(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("  → [API] Create warehouse pick ticket"); // => Warehouse system API
    // => Log for observability
  }

  private updateInventoryReservation(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("  → [DB] Reserve inventory"); // => Database update
    // => Log for observability
  }

  private generateTrackingNumber(): string {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    return "TRACK-" + Date.now(); // => Mock tracking number
  }

  private updateInventoryActual(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("  → [DB] Decrement inventory"); // => Database update
    // => Log for observability
  }

  private notifyDeliveryPartner(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("  → [API] Notify delivery partner"); // => Shipping API
    // => Log for observability
  }

  private updateCustomerLoyaltyPoints(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("  → [DB] Add loyalty points"); // => Database update
    // => Log for observability
  }

  private closeWarehouseTicket(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("  → [API] Close warehouse ticket"); // => Warehouse system API
    // => Log for observability
  }

  private archiveOrder(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("  → [DB] Archive order"); // => Database update
    // => Log for observability
  }
}

// Usage
const order = new OrderFulfillment({
  // => Instance creation via constructor
  // => Create new instance
  // => Create new instance
  // => Initialize order
  // => state: "Pending"
  orderId: "ORD-123", // => Statement execution
  customerId: "CUST-456", // => Statement execution
}); // => Statement execution

order.handleEvent("confirm"); // => Pending → Confirmed
// => Processes events, triggers transitions
// => Output:
// → Transition: Confirm order
//   → [API] Charge payment
//   → [API] Send confirmation email to CUST-456
//   → [API] Create warehouse pick ticket
//   → [DB] Reserve inventory
//   Order ORD-123 confirmed

order.handleEvent("ship"); // => Confirmed → Shipped
// => Processes events, triggers transitions
// => Output:
// → Transition: Ship order
//   → [DB] Decrement inventory
//   → [API] Send shipped email to CUST-456
//   → [API] Notify delivery partner
//   Order ORD-123 shipped (tracking: TRACK-1234567890)

order.handleEvent("deliver"); // => Shipped → Delivered
// => Processes events, triggers transitions
// => Output:
// → Transition: Deliver order
//   → [API] Send delivered email to CUST-456
//   → [DB] Add loyalty points
//   → [API] Close warehouse ticket
//   → [DB] Archive order
//   Order ORD-123 delivered

Key Takeaway: Transition actions orchestrate multiple external side effects (API calls, database updates, notifications) that must occur during specific state transitions. Each transition has checklist of operations to perform.

Why It Matters: Explicit transition actions ensure side effects happen reliably. Order systems experience incomplete side effects (payment charged but no inventory reserved, or warehouse notified but no customer email). Consolidating side effects into transition actions with comprehensive error handling reduces incomplete orders significantly. Transition actions become auditable checklist ensuring all required operations complete.

Example 26: Conditional Transition Actions

Transition actions can execute conditionally based on transition context or data.

TypeScript Implementation:

// User onboarding FSM with conditional transition actions
type OnboardingState = "NotStarted" | "ProfileCreated" | "Verified" | "Completed"; // => Four states
// => Type system ensures only valid states used
type OnboardingEvent = "create_profile" | "verify_email" | "complete_onboarding"; // => Three events
// => Events trigger state transitions

interface UserProfile {
  // => Type declaration defines structure
  // => Begin object/config definition
  email: string; // => User email
  isPremium: boolean; // => Premium status
  referralCode?: string; // => Referral code (optional)
  // => FSM state management logic
}

class UserOnboarding {
  // => Class encapsulates FSM logic
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: OnboardingState = "NotStarted"; // => Initial: NotStarted
  // => FSM begins execution in NotStarted state
  private profile: UserProfile | null = null; // => User profile
  // => Initialized alongside FSM state

  getCurrentState(): OnboardingState {
    // => Accessor method definition
    // => Query method: read current FSM state
    // => Pure read, no side effects
    return this.state; // => Returns state
  }

  handleEvent(event: OnboardingEvent, data?: any): void {
    // => Event handler method
    // => Ternary: condition ? true_branch : false_branch
    // => Event handler: main FSM dispatch method
    // => Processes events, triggers transitions
    if (event === "create_profile" && this.state === "NotStarted") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Event type check
      // => Combined (state, event) guard
      this.profile = data; // => Store profile
      this.onTransition_CreateProfile(); // => Transition action with conditionals
      this.state = "ProfileCreated"; // => NotStarted → ProfileCreated
    } else if (event === "verify_email" && this.state === "ProfileCreated") {
      // => Method signature: defines function interface
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.onTransition_VerifyEmail(); // => Transition action with conditionals
      this.state = "Verified"; // => ProfileCreated → Verified
    } else if (event === "complete_onboarding" && this.state === "Verified") {
      // => Method signature: defines function interface
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Alternative condition
      this.onTransition_CompleteOnboarding(); // => Transition action with conditionals
      this.state = "Completed"; // => Verified → Completed
    }
  }

  // TRANSITION ACTION: NotStarted → ProfileCreated (conditional logic)
  private onTransition_CreateProfile(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("→ Transition: Create profile"); // => Output for verification
    // => Debug/audit output
    // => Log for observability

    if (this.profile!.referralCode) {
      // => Conditional branch
      // => Conditional check
      // => Branch execution based on condition
      // => Conditional: has referral?
      console.log("  → Apply referral bonus"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      this.applyReferralBonus(this.profile!.referralCode); // => Execute if referred
    }

    if (this.profile!.isPremium) {
      // => Conditional branch
      // => Conditional check
      // => Branch execution based on condition
      // => Conditional: premium user?
      console.log("  → Enable premium features"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      this.enablePremiumFeatures(); // => Execute if premium
    } else {
      // => Statement execution
      // => Fallback branch
      console.log("  → Show upgrade prompt"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      this.showUpgradePrompt(); // => Execute if free tier
    }

    this.sendWelcomeEmail(); // => Always execute
  }

  // TRANSITION ACTION: ProfileCreated → Verified (conditional logic)
  private onTransition_VerifyEmail(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("→ Transition: Verify email"); // => Output for verification
    // => Debug/audit output
    // => Log for observability

    if (this.profile!.isPremium) {
      // => Conditional branch
      // => Conditional check
      // => Branch execution based on condition
      // => Conditional: premium user?
      console.log("  → Grant immediate access"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      this.grantImmediateAccess(); // => Skip additional verification
    } else {
      // => Statement execution
      // => Fallback branch
      console.log("  → Require phone verification"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      this.requestPhoneVerification(); // => Additional step for free tier
    }
  }

  // TRANSITION ACTION: Verified → Completed (conditional logic)
  private onTransition_CompleteOnboarding(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("→ Transition: Complete onboarding"); // => Output for verification
    // => Debug/audit output
    // => Log for observability

    if (this.profile!.referralCode) {
      // => Conditional branch
      // => Conditional check
      // => Branch execution based on condition
      // => Conditional: referred user?
      console.log("  → Credit referrer account"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      this.creditReferrer(this.profile!.referralCode); // => Reward referrer
    }

    if (this.profile!.isPremium) {
      // => Conditional branch
      // => Conditional check
      // => Branch execution based on condition
      // => Conditional: premium?
      console.log("  → Assign dedicated support"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      this.assignDedicatedSupport(); // => Premium perk
    }

    this.trackOnboardingComplete(); // => Always execute
  }

  // Helper methods
  private applyReferralBonus(code: string): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log(`    Referral code ${code} applied`); // => Output for verification
    // => Debug/audit output
    // => Log for observability
  }

  private enablePremiumFeatures(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log(`    Premium features enabled`); // => Output for verification
    // => Debug/audit output
    // => Log for observability
  }

  private showUpgradePrompt(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log(`    Upgrade prompt displayed`); // => Output for verification
    // => Debug/audit output
    // => Log for observability
  }

  private sendWelcomeEmail(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log(`    Welcome email sent to ${this.profile!.email}`); // => Output for verification
    // => Debug/audit output
    // => Log for observability
  }

  private grantImmediateAccess(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log(`    Full access granted`); // => Output for verification
    // => Debug/audit output
    // => Log for observability
  }

  private requestPhoneVerification(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log(`    Phone verification required`); // => Output for verification
    // => Debug/audit output
    // => Log for observability
  }

  private creditReferrer(code: string): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log(`    Referrer ${code} credited`); // => Output for verification
    // => Debug/audit output
    // => Log for observability
  }

  private assignDedicatedSupport(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log(`    Dedicated support assigned`); // => Output for verification
    // => Debug/audit output
    // => Log for observability
  }

  private trackOnboardingComplete(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log(`    Analytics: onboarding_complete`); // => Output for verification
    // => Debug/audit output
    // => Log for observability
  }
}

// Usage - Free tier user with referral
const user1 = new UserOnboarding(); // => state: "NotStarted"
user1.handleEvent("create_profile", {
  // => Event handler method
  // => Event handler: main FSM dispatch method
  // => Processes events, triggers transitions
  email: "user@example.com", // => Statement execution
  isPremium: false, // => Statement execution
  referralCode: "REF123", // => Statement execution
}); // => Statement execution
// => Output:
// → Transition: Create profile
//   → Apply referral bonus
//     Referral code REF123 applied
//   → Show upgrade prompt
//     Upgrade prompt displayed
//     Welcome email sent to user@example.com

// Usage - Premium user without referral
const user2 = new UserOnboarding(); // => state: "NotStarted"
user2.handleEvent("create_profile", {
  // => Event handler method
  // => Event handler: main FSM dispatch method
  // => Processes events, triggers transitions
  email: "premium@example.com", // => Statement execution
  isPremium: true, // => Statement execution
}); // => Statement execution
// => Output:
// → Transition: Create profile
//   → Enable premium features
//     Premium features enabled
//     Welcome email sent to premium@example.com

Key Takeaway: Transition actions can contain conditional logic, executing different operations based on transition context (user data, flags, external state). Enables flexible state transitions adapting to varying circumstances.

Why It Matters: Conditional transition actions handle business logic variations without state explosion. When LinkedIn implemented profile completion FSMs, conditional actions (premium vs free users) prevented needing separate FSMs for each user tier. One FSM with conditional transition actions reduced code complexity by 60% while supporting 5 user tiers and 10+ feature flags.

Example 27: Transition Action Error Handling

Transition actions must handle errors gracefully, potentially reverting state or moving to error states.

TypeScript Implementation:

// File upload FSM with error handling in transition actions
type UploadState = "Idle" | "Uploading" | "ProcessingData" | "Success" | "Failed"; // => Five states
// => Type system ensures only valid states used
type UploadEvent = "start_upload" | "upload_complete" | "processing_complete"; // => Three events
// => Events trigger state transitions

class FileUpload {
  // => Class encapsulates FSM logic
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: UploadState = "Idle"; // => Initial: Idle
  // => FSM begins execution in Idle state
  private fileName: string | null = null; // => Uploaded file name
  // => Initialized alongside FSM state
  private errorMessage: string | null = null; // => Error details
  // => Initialized alongside FSM state

  getCurrentState(): UploadState {
    // => Accessor method definition
    // => Query method: read current FSM state
    // => Pure read, no side effects
    return this.state; // => Returns state
  }

  getError(): string | null {
    // => Accessor method definition
    // => Begin object/config definition
    return this.errorMessage; // => Returns error if Failed
  }

  handleEvent(event: UploadEvent, data?: any): void {
    // => Event handler method
    // => Ternary: condition ? true_branch : false_branch
    // => Event handler: main FSM dispatch method
    // => Processes events, triggers transitions
    try {
      // => Statement execution
      // => Begin error handling
      // => Begin error handling
      // => Begin object/config definition
      if (event === "start_upload" && this.state === "Idle") {
        // => Event type guard condition
        // => Logical AND: both conditions must be true
        // => Comparison check
        // => Comparison check
        // => Event type check
        // => Combined (state, event) guard
        this.fileName = data; // => Store filename
        this.onTransition_StartUpload(); // => May throw error
        this.state = "Uploading"; // => Idle → Uploading
      } else if (event === "upload_complete" && this.state === "Uploading") {
        // => Method signature: defines function interface
        // => Logical AND: both conditions must be true
        // => Comparison check
        // => Comparison check
        // => Alternative condition
        this.onTransition_ProcessUpload(); // => May throw error
        this.state = "ProcessingData"; // => Uploading → ProcessingData
      } else if (event === "processing_complete" && this.state === "ProcessingData") {
        // => Method signature: defines function interface
        // => Logical AND: both conditions must be true
        // => Comparison check
        // => Comparison check
        // => Alternative condition
        this.onTransition_FinalizeUpload(); // => May throw error
        this.state = "Success"; // => ProcessingData → Success
      }
    } catch (error) {
      // => Method signature: defines function interface
      // => Catch errors
      // => Catch errors
      // => Begin object/config definition
      this.handleTransitionError(error as Error); // => Error handler
    }
  }

  // TRANSITION ACTION: Idle → Uploading (with error handling)
  private onTransition_StartUpload(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("→ Transition: Start upload"); // => Output for verification
    // => Debug/audit output
    // => Log for observability

    if (!this.fileName || this.fileName.length === 0) {
      // => Conditional branch
      // => Logical OR: either condition can be true
      // => Comparison check
      // => Comparison check
      // => Conditional check
      // => Branch execution based on condition
      // => Validation check
      throw new Error("Invalid filename"); // => Throw error on failure
      // => Fail fast on FSM violation
    }

    if (this.fileName.endsWith(".exe")) {
      // => Conditional branch
      // => Chained method calls or nested operations
      // => Conditional check
      // => Branch execution based on condition
      // => Security check
      throw new Error("Executable files not allowed"); // => Throw error
      // => Fail fast on FSM violation
    }

    console.log(`  Upload started: ${this.fileName}`); // => Output for verification
    // => Debug/audit output
    // => Log for observability
    this.initializeUploadStream(); // => Begin upload
  }

  // TRANSITION ACTION: Uploading → ProcessingData (with error handling)
  private onTransition_ProcessUpload(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("→ Transition: Process uploaded file"); // => Output for verification
    // => Debug/audit output
    // => Log for observability

    const fileSize = this.checkFileSize(); // => Check size
    if (fileSize > 100 * 1024 * 1024) {
      // => Conditional branch
      // => Conditional check
      // => Branch execution based on condition
      // => Exceeds 100MB?
      throw new Error("File too large (max 100MB)"); // => Throw error
      // => FSM state management logic
      // => Fail fast on FSM violation
    }

    if (!this.validateFileIntegrity()) {
      // => Conditional branch
      // => Chained method calls or nested operations
      // => Conditional check
      // => Branch execution based on condition
      // => Check integrity
      throw new Error("File corrupted during upload"); // => Throw error
      // => Fail fast on FSM violation
    }

    console.log(`  Processing file (${fileSize} bytes)`); // => Output for verification
    // => Chained method calls or nested operations
    // => Debug/audit output
    // => Log for observability
    this.runVirusScan(); // => Security scan
  }

  // TRANSITION ACTION: ProcessingData → Success (with error handling)
  private onTransition_FinalizeUpload(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("→ Transition: Finalize upload"); // => Output for verification
    // => Debug/audit output
    // => Log for observability

    if (!this.generateThumbnail()) {
      // => Conditional branch
      // => Chained method calls or nested operations
      // => Conditional check
      // => Branch execution based on condition
      // => Try thumbnail generation
      console.warn("  Warning: Thumbnail generation failed (non-fatal)"); // => Log warning but continue
      // => FSM state management logic
    }

    this.saveToDatabase(); // => May throw error
    this.sendNotification(); // => May throw error
    console.log(`  Upload complete: ${this.fileName}`); // => Output for verification
    // => Debug/audit output
    // => Log for observability
  }

  // ERROR HANDLER (transitions to Failed state)
  private handleTransitionError(error: Error): void {
    // => Event handler method
    // => Extended state (data beyond FSM state)
    console.error(`✗ Transition error: ${error.message}`); // => Method invocation
    this.errorMessage = error.message; // => Store error message
    this.state = "Failed"; // => Any → Failed (error recovery)
    this.cleanupResources(); // => Cleanup on error
    console.log(`  State: ${this.state}, Error: ${this.errorMessage}`); // => Output for verification
    // => Debug/audit output
    // => Log for observability
  }

  // Helper methods
  private initializeUploadStream(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("  → Initialize upload stream"); // => Output for verification
    // => Debug/audit output
    // => Log for observability
  }

  private checkFileSize(): number {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    return 50 * 1024 * 1024; // => Mock: 50MB
  }

  private validateFileIntegrity(): boolean {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    return true; // => Mock: validation pass
  }

  private runVirusScan(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("  → Virus scan complete"); // => Output for verification
    // => Debug/audit output
    // => Log for observability
  }

  private generateThumbnail(): boolean {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    return true; // => Mock: thumbnail generated
  }

  private saveToDatabase(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("  → Saved to database"); // => Output for verification
    // => Debug/audit output
    // => Log for observability
  }

  private sendNotification(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("  → Notification sent"); // => Output for verification
    // => Debug/audit output
    // => Log for observability
  }

  private cleanupResources(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("  → Cleanup resources"); // => Output for verification
    // => Debug/audit output
    // => Log for observability
  }
}

// Usage - Successful upload
const upload1 = new FileUpload(); // => state: "Idle"
upload1.handleEvent("start_upload", "document.pdf"); // => Idle → Uploading
// => Processes events, triggers transitions
upload1.handleEvent("upload_complete"); // => Uploading → ProcessingData
// => Processes events, triggers transitions
upload1.handleEvent("processing_complete"); // => ProcessingData → Success
// => Processes events, triggers transitions
console.log(`Final state: ${upload1.getCurrentState()}`); // => Output: Success
// => FSM state management logic
// => Pure read, no side effects

// Usage - Failed upload (invalid file type)
const upload2 = new FileUpload(); // => state: "Idle"
upload2.handleEvent("start_upload", "malware.exe"); // => Error: Executable not allowed
// => Processes events, triggers transitions
// => Output:
// → Transition: Start upload
// ✗ Transition error: Executable files not allowed
//   → Cleanup resources
//   State: Failed, Error: Executable files not allowed

console.log(`Final state: ${upload2.getCurrentState()}`); // => Output: Failed
// => FSM state management logic
// => Pure read, no side effects
console.log(`Error: ${upload2.getError()}`); // => Output: Executable files not allowed
// => FSM state management logic

Key Takeaway: Transition actions must handle errors gracefully. Try-catch blocks capture errors during transitions; error handlers move FSM to Failed state and cleanup resources. Distinguishes fatal errors (transition fails) from warnings (log but continue).

Why It Matters: Robust error handling prevents stuck FSMs. File upload systems experience failures where uploads leave FSMs in inconsistent states (partially uploaded files, orphaned database records, memory leaks). Adding error handlers to transition actions (catch errors → transition to Failed state → cleanup resources) significantly reduces stuck uploads. Failed state is terminal, preventing retry loops and exposing errors to users/monitoring systems.

Simple State Patterns (Examples 28-30)

Example 28: State Pattern Object-Oriented Design

The State Pattern encapsulates state-specific behavior in separate classes, delegating behavior to current state object.

TypeScript Implementation:

// TCP connection using State Pattern (OOP design)

// State interface defining common operations
interface ConnectionState {
  // => Type declaration defines structure
  // => Begin object/config definition
  open(context: TcpConnection): void; // => Open operation
  close(context: TcpConnection): void; // => Close operation
  send(context: TcpConnection, data: string): void; // => Send operation
  getStateName(): string; // => State name
}

// Concrete state: Closed
class ClosedState implements ConnectionState {
  // => Class encapsulates FSM logic
  // => State machine implementation class
  // => Encapsulates state + transition logic
  open(context: TcpConnection): void {
    // => Method invocation
    // => Begin object/config definition
    console.log("Opening connection..."); // => Log action
    // => Log for observability
    context.setState(new ConnectingState()); // => Transition to Connecting
    // => FSM state management logic
  }

  close(context: TcpConnection): void {
    // => Method invocation
    // => Begin object/config definition
    console.log("Already closed"); // => No-op
    // => Log for observability
  }

  send(context: TcpConnection, data: string): void {
    // => Method invocation
    // => Begin object/config definition
    throw new Error("Cannot send: connection closed"); // => Invalid operation
    // => Fail fast on FSM violation
  }

  getStateName(): string {
    // => Accessor method definition
    // => Begin object/config definition
    return "Closed"; // => State name
  }
}

// Concrete state: Connecting
class ConnectingState implements ConnectionState {
  // => Class encapsulates FSM logic
  // => State machine implementation class
  // => Encapsulates state + transition logic
  open(context: TcpConnection): void {
    // => Method invocation
    // => Begin object/config definition
    console.log("Already connecting"); // => No-op
    // => Log for observability
  }

  close(context: TcpConnection): void {
    // => Method invocation
    // => Begin object/config definition
    console.log("Abort connection attempt"); // => Cancel connection
    // => Log for observability
    context.setState(new ClosedState()); // => Transition to Closed
    // => FSM state management logic
  }

  send(context: TcpConnection, data: string): void {
    // => Method invocation
    // => Begin object/config definition
    throw new Error("Cannot send: still connecting"); // => Invalid operation
    // => Fail fast on FSM violation
  }

  getStateName(): string {
    // => Accessor method definition
    // => Begin object/config definition
    return "Connecting"; // => State name
  }

  // Simulate connection establishment
  completeConnection(context: TcpConnection): void {
    // => Method invocation
    // => Begin object/config definition
    console.log("Connection established"); // => Log success
    // => Log for observability
    context.setState(new OpenState()); // => Transition to Open
    // => FSM state management logic
  }
}

// Concrete state: Open
class OpenState implements ConnectionState {
  // => Class encapsulates FSM logic
  // => State machine implementation class
  // => Encapsulates state + transition logic
  open(context: TcpConnection): void {
    // => Method invocation
    // => Begin object/config definition
    console.log("Already open"); // => No-op
    // => Log for observability
  }

  close(context: TcpConnection): void {
    // => Method invocation
    // => Begin object/config definition
    console.log("Closing connection..."); // => Log action
    // => Log for observability
    context.setState(new ClosedState()); // => Transition to Closed
    // => FSM state management logic
  }

  send(context: TcpConnection, data: string): void {
    // => Method invocation
    // => Begin object/config definition
    console.log(`Sending data: ${data}`); // => Send data
    // => FSM state management logic
    // => Log for observability
    // => Actual send logic here
  }

  getStateName(): string {
    // => Accessor method definition
    // => Begin object/config definition
    return "Open"; // => State name
  }
}

// Context class delegating to state objects
class TcpConnection {
  // => Class encapsulates FSM logic
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: ConnectionState; // => Current state object

  constructor() {
    // => Method invocation
    // => Begin object/config definition
    this.state = new ClosedState(); // => Initial state: Closed
    // => FSM state management logic
  }

  setState(state: ConnectionState): void {
    // => Accessor method definition
    // => Begin object/config definition
    console.log(`State transition: → ${state.getStateName()}`); // => Output for verification
    // => Chained method calls or nested operations
    // => Debug/audit output
    // => Log for observability
    this.state = state; // => Change state object
  }

  getStateName(): string {
    // => Accessor method definition
    // => Begin object/config definition
    return this.state.getStateName(); // => Delegate to state
  }

  // Public API methods delegate to current state
  open(): void {
    // => Method invocation
    // => Begin object/config definition
    this.state.open(this); // => Delegate to state
  }

  close(): void {
    // => Method invocation
    // => Begin object/config definition
    this.state.close(this); // => Delegate to state
  }

  send(data: string): void {
    // => Method invocation
    // => Begin object/config definition
    this.state.send(this, data); // => Delegate to state
  }
}

// Usage
const connection = new TcpConnection(); // => state: Closed
console.log(`Current: ${connection.getStateName()}`); // => Output: Closed
// => FSM state management logic

connection.open(); // => Closed → Connecting
// => Output:
// Opening connection...
// State transition: → Connecting

try {
  // => Statement execution
  // => Begin error handling
  // => Begin error handling
  // => Begin object/config definition
  connection.send("Hello"); // => Invalid: cannot send while Connecting
} catch (error) {
  // => Method signature: defines function interface
  // => Catch errors
  // => Catch errors
  // => Begin object/config definition
  console.log(`Error: ${(error as Error).message}`); // => Output: Cannot send: still connecting
  // => FSM state management logic
}

// Simulate connection completing
const connectingState = new ConnectingState(); // => State variable initialization
// => Create new instance
// => Create new instance
// => Initialize connectingState
connectingState.completeConnection(connection); // => Connecting → Open
// => Output:
// Connection established
// State transition: → Open

connection.send("Hello"); // => Valid: send while Open
// => Output: Sending data: Hello

connection.close(); // => Open → Closed
// => Output:
// Closing connection...
// State transition: → Closed

Key Takeaway: State Pattern encapsulates state-specific behavior in separate classes. Context delegates operations to current state object. Adding new states requires creating new state class, not modifying existing code (Open/Closed Principle).

Why It Matters: State Pattern reduces conditional complexity. Document editing systems experience numerous if-statements checking document state (ReadOnly, Editing, Reviewing, etc.) scattered across many classes. Refactoring to State Pattern (one class per document state) reduces cyclomatic complexity significantly per method, eliminates state-related bugs, and makes adding new states (Collaborative mode) trivial.

Example 29: Hierarchical State Machines (Nested States)

Complex FSMs benefit from hierarchical states where substates inherit parent state behavior.

TypeScript Implementation:

// Alarm system with hierarchical states

// Parent state: Armed (has substates)
type ArmedSubstate = "ArmedAway" | "ArmedStay" | "ArmedNight"; // => Type declaration defines structure
// => Assign value
// => Assign value
// Parent state: Disarmed (has substates)
type DisarmedSubstate = "DisarmedNormal" | "DisarmedMaintenance"; // => Type declaration defines structure
// => Assign value
// => Assign value
// Top-level states
type AlarmState = ArmedSubstate | DisarmedSubstate | "Triggered"; // => Type declaration defines structure
// => Assign value
// => Assign value
// => Enum-like union type for state values
// => Type system ensures only valid states used

type AlarmEvent = "arm_away" | "arm_stay" | "arm_night" | "disarm" | "sensor_triggered" | "enter_maintenance"; // => Type declaration defines structure
// => Assign value
// => Assign value
// => Defines event alphabet for FSM
// => Events trigger state transitions

class HierarchicalAlarm {
  // => Class encapsulates FSM logic
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: AlarmState = "DisarmedNormal"; // => Initial: DisarmedNormal
  // => FSM begins execution in DisarmedNormal state

  getCurrentState(): AlarmState {
    // => Accessor method definition
    // => Query method: read current FSM state
    // => Pure read, no side effects
    return this.state; // => Returns state
  }

  handleEvent(event: AlarmEvent): void {
    // => Event handler method
    // => Event handler: main FSM dispatch method
    // => Processes events, triggers transitions
    console.log(`Event: ${event} in state ${this.state}`); // => Output for verification
    // => Debug/audit output
    // => Log for observability

    // Parent state behavior: ANY Armed substate can be triggered
    if (this.isArmed() && event === "sensor_triggered") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Comparison check
      // => Comparison check
      // => Event type check
      this.state = "Triggered"; // => Any Armed → Triggered
      this.soundAlarm(); // => Parent behavior
      return; // => Statement execution
    }

    // Parent state behavior: ANY state can disarm
    if (event === "disarm") {
      // => Event type guard condition
      // => Comparison check
      // => Comparison check
      // => Event type check
      this.state = "DisarmedNormal"; // => Any → DisarmedNormal
      this.stopAlarm(); // => Parent behavior
      return; // => Statement execution
    }

    // Specific state transitions
    if (this.isDisarmed()) {
      // => Conditional branch
      // => Chained method calls or nested operations
      // => Conditional check
      // => Branch execution based on condition
      if (event === "arm_away") {
        // => Event type guard condition
        // => Comparison check
        // => Comparison check
        // => Event type check
        this.state = "ArmedAway"; // => Disarmed → ArmedAway
        this.activateSensors("all"); // => Specific behavior
      } else if (event === "arm_stay") {
        // => Method signature: defines function interface
        // => Comparison check
        // => Comparison check
        // => Alternative condition
        this.state = "ArmedStay"; // => Disarmed → ArmedStay
        this.activateSensors("perimeter"); // => Specific behavior
      } else if (event === "arm_night") {
        // => Method signature: defines function interface
        // => Comparison check
        // => Comparison check
        // => Alternative condition
        this.state = "ArmedNight"; // => Disarmed → ArmedNight
        this.activateSensors("night_mode"); // => Specific behavior
      } else if (event === "enter_maintenance") {
        // => Method signature: defines function interface
        // => Comparison check
        // => Comparison check
        // => Alternative condition
        this.state = "DisarmedMaintenance"; // => DisarmedNormal → DisarmedMaintenance
      }
    }
  }

  // Helper: Check if in ANY Armed substate (parent state check)
  private isArmed(): boolean {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    return this.state === "ArmedAway" || this.state === "ArmedStay" || this.state === "ArmedNight"; // => State transition execution
    // => Logical OR: either condition can be true
    // => Access instance property
    // => Access instance property
    // => Return current state value
    // => Hierarchical check: in parent "Armed" state?
  }

  // Helper: Check if in ANY Disarmed substate (parent state check)
  private isDisarmed(): boolean {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    return this.state === "DisarmedNormal" || this.state === "DisarmedMaintenance"; // => State transition execution
    // => Logical OR: either condition can be true
    // => Access instance property
    // => Access instance property
    // => Return current state value
    // => Hierarchical check: in parent "Disarmed" state?
  }

  private soundAlarm(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("  🚨 ALARM TRIGGERED!"); // => Parent state action
    // => Log for observability
  }

  private stopAlarm(): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log("  ✓ Alarm silenced"); // => Parent state action
    // => Log for observability
  }

  private activateSensors(mode: string): void {
    // => Method invocation
    // => Extended state (data beyond FSM state)
    console.log(`  → Activate sensors: ${mode}`); // => Substate-specific action
    // => FSM state management logic
    // => Log for observability
  }
}

// Usage
const alarm = new HierarchicalAlarm(); // => state: DisarmedNormal

alarm.handleEvent("arm_away"); // => DisarmedNormal → ArmedAway
// => Processes events, triggers transitions
// => Output:
// Event: arm_away in state DisarmedNormal
//   → Activate sensors: all

alarm.handleEvent("sensor_triggered"); // => ArmedAway → Triggered (parent transition)
// => FSM state management logic
// => Processes events, triggers transitions
// => Output:
// Event: sensor_triggered in state ArmedAway
//   🚨 ALARM TRIGGERED!

alarm.handleEvent("disarm"); // => Triggered → DisarmedNormal (parent transition)
// => FSM state management logic
// => Processes events, triggers transitions
// => Output:
// Event: disarm in state Triggered
//   ✓ Alarm silenced

alarm.handleEvent("arm_stay"); // => DisarmedNormal → ArmedStay
// => Processes events, triggers transitions
// => Output:
// Event: arm_stay in state DisarmedNormal
//   → Activate sensors: perimeter

alarm.handleEvent("sensor_triggered"); // => ArmedStay → Triggered (parent transition)
// => FSM state management logic
// => Processes events, triggers transitions
// => Output:
// Event: sensor_triggered in state ArmedStay
//   🚨 ALARM TRIGGERED!

Key Takeaway: Hierarchical FSMs group related substates under parent states. Parent state defines common behavior (ANY Armed substate can trigger alarm). Substates specialize parent behavior (ArmedAway activates all sensors, ArmedStay only perimeter). Reduces duplication and makes state relationships explicit.

Why It Matters: Hierarchical states prevent state explosion. When ADT (security systems) modeled alarm modes, flat FSM required 15 states and 100+ transitions. Hierarchical FSM (Armed{Away,Stay,Night}, Disarmed{Normal,Maintenance}, Triggered) reduced to 6 states and 20 transitions while supporting same functionality. Parent state transitions (disarm from ANY state) eliminated 10 duplicate transition rules.

Example 30: Simple FSM Table-Driven Implementation

Transition tables enable data-driven FSM implementation, separating state logic from implementation.

TypeScript Implementation:

// Turnstile FSM using transition table

type TurnstileState = "Locked" | "Unlocked"; // => Two states
// => Type system ensures only valid states used
type TurnstileEvent = "coin" | "push"; // => Two events
// => Events trigger state transitions

// Transition table: [currentState][event] => newState
const transitionTable: Record<TurnstileState, Partial<Record<TurnstileEvent, TurnstileState>>> = {
  // => State variable initialization
  // => Assign value
  // => Assign value
  // => Type: transition table structure
  // => Maps (currentState, event) -> nextState
  Locked: {
    // => Statement execution
    // => Begin object/config definition
    coin: "Unlocked", // => Locked + coin → Unlocked
    push: "Locked", // => Locked + push → Locked (self-transition)
  },
  Unlocked: {
    // => Statement execution
    // => Begin object/config definition
    coin: "Unlocked", // => Unlocked + coin → Unlocked (self-transition)
    push: "Locked", // => Unlocked + push → Locked
  },
};

// Action table: [currentState][event] => action to execute
const actionTable: Record<TurnstileState, Partial<Record<TurnstileEvent, () => void>>> = {
  // => State variable initialization
  // => Additional context for complex operation
  // => Assign value
  // => Assign value
  // => Type: transition table structure
  // => Maps (currentState, event) -> nextState
  Locked: {
    // => Statement execution
    // => Begin object/config definition
    coin: () => console.log("  → Unlock turnstile"), // => Action for Locked + coin
    // => FSM state management logic
    // => Log for observability
    push: () => console.log("  → Alarm: Push while locked"), // => Action for Locked + push
    // => FSM state management logic
    // => Log for observability
  },
  Unlocked: {
    // => Statement execution
    // => Begin object/config definition
    coin: () => console.log("  → Return coin (already unlocked)"), // => Action for Unlocked + coin
    // => FSM state management logic
    // => Log for observability
    push: () => console.log("  → Lock turnstile after passage"), // => Action for Unlocked + push
    // => FSM state management logic
    // => Log for observability
  },
};

class TurnstileFSM {
  // => Class encapsulates FSM logic
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: TurnstileState = "Locked"; // => Initial: Locked
  // => FSM begins execution in Locked state

  getCurrentState(): TurnstileState {
    // => Accessor method definition
    // => Query method: read current FSM state
    // => Pure read, no side effects
    return this.state; // => Returns state
  }

  handleEvent(event: TurnstileEvent): void {
    // => Event handler method
    // => Event handler: main FSM dispatch method
    // => Processes events, triggers transitions
    console.log(`Event: ${event} (State: ${this.state})`); // => Output for verification
    // => Chained method calls or nested operations
    // => Debug/audit output
    // => Log for observability

    // Look up next state in transition table
    const nextState = transitionTable[this.state][event]; // => Table lookup
    if (!nextState) {
      // => Conditional branch
      // => Conditional check
      // => Branch execution based on condition
      console.log("  ✗ Invalid transition"); // => No transition defined
      // => Log for observability
      return; // => Statement execution
    }

    // Execute action from action table
    const action = actionTable[this.state][event]; // => Table lookup
    if (action) {
      // => Conditional branch
      // => Conditional check
      // => Branch execution based on condition
      action(); // => Execute action
    }

    // Transition to next state
    if (this.state !== nextState) {
      // => State-based guard condition
      // => Comparison check
      // => Comparison check
      // => Conditional check
      // => Branch execution based on condition
      console.log(`  State: ${this.state}${nextState}`); // => Log transition
      // => FSM state management logic
      // => Log for observability
      this.state = nextState; // => Change state
    }
  }
}

// Usage
const turnstile = new TurnstileFSM(); // => state: "Locked"
console.log(`Initial: ${turnstile.getCurrentState()}`); // => Output: Locked
// => FSM state management logic
// => Pure read, no side effects

turnstile.handleEvent("push"); // => Locked + push → Locked
// => Processes events, triggers transitions
// => Output:
// Event: push (State: Locked)
//   → Alarm: Push while locked

turnstile.handleEvent("coin"); // => Locked + coin → Unlocked
// => Processes events, triggers transitions
// => Output:
// Event: coin (State: Locked)
//   → Unlock turnstile
//   State: Locked → Unlocked

turnstile.handleEvent("coin"); // => Unlocked + coin → Unlocked
// => Processes events, triggers transitions
// => Output:
// Event: coin (State: Unlocked)
//   → Return coin (already unlocked)

turnstile.handleEvent("push"); // => Unlocked + push → Locked
// => Processes events, triggers transitions
// => Output:
// Event: push (State: Unlocked)
//   → Lock turnstile after passage
//   State: Unlocked → Locked

console.log(`Final: ${turnstile.getCurrentState()}`); // => Output: Locked
// => FSM state management logic
// => Pure read, no side effects

Key Takeaway: Table-driven FSMs separate state logic (transition tables, action tables) from implementation (handleEvent method). Adding states/transitions requires only table updates, not code changes. Transition table defines valid transitions; action table defines operations to execute.

Why It Matters: Table-driven FSMs enable non-programmers to configure state logic. Call routing systems use transition tables to allow customer support to configure call flows (IVR menus, voicemail, transfers) without engineering involvement. Business users edit JSON configuration files defining transition tables; FSM engine executes flows. This reduces call flow deployment time significantly.


This completes the beginner-level FSM by-example tutorial with 30 comprehensive examples covering introduction to FSMs, basic states and transitions, events and triggers, guards and conditions, entry/exit actions, transition actions, and simple state patterns (0-40% coverage).

Last updated