Skip to content
AyoKoding

Intermediate

This intermediate-level tutorial explores advanced FSM concepts through 30 annotated code examples, covering hierarchical states, composite states, parallel states, history states, the State Pattern implementation, and real-world production workflows like order processing and authentication flows.

Hierarchical States (Examples 31-35)

Example 31: What are Hierarchical States?

Hierarchical states (also called nested states) enable state composition where substates inherit behavior from parent states. This reduces duplication by defining common transitions at the parent level while specializing behavior in substates.

stateDiagram-v2
    [*] --> Disconnected
 
    state Connected {
        [*] --> Idle
        Idle --> Active: activity
        Active --> Idle: pause
    }
 
    Disconnected --> Connected: connect
    Connected --> Disconnected: disconnect
 
    classDef disconnectedState fill:#0173B2,stroke:#000,color:#fff
    classDef connectedState fill:#DE8F05,stroke:#000,color:#fff
    classDef idleState fill:#029E73,stroke:#000,color:#fff
    classDef activeState fill:#CC78BC,stroke:#000,color:#fff
 
    class Disconnected disconnectedState
    class Connected connectedState
    class Idle idleState
    class Active activeState

Key Concept: Connected is a parent state containing substates (Idle, Active). The "disconnect" transition applies to ALL substates - you can disconnect from Idle or Active without duplicating the transition.

Minimal TypeScript Example:

// Hierarchical state structure: parent state contains substates
type ConnState = "Disconnected" | "Connected.Idle" | "Connected.Active"; // => Dot notation represents hierarchy
type ConnEvent = "connect" | "disconnect" | "activity" | "pause";
 
class Connection {
  private state: ConnState = "Disconnected"; // => Initial state: top-level
 
  handleEvent(event: ConnEvent): void {
    // Parent-level transition: applies regardless of substate
    if (event === "disconnect" && this.state.startsWith("Connected")) {
      this.state = "Disconnected"; // => Parent transition: Connected.* → Disconnected (any substate)
      return;
    }
    // Substate transitions within Connected
    if (this.state === "Disconnected" && event === "connect") {
      this.state = "Connected.Idle"; // => Transition: Disconnected → Connected.Idle (default substate)
    } else if (this.state === "Connected.Idle" && event === "activity") {
      this.state = "Connected.Active"; // => Substate transition: Idle → Active
    } else if (this.state === "Connected.Active" && event === "pause") {
      this.state = "Connected.Idle"; // => Substate transition: Active → Idle
    }
  }
 
  getState(): ConnState {
    return this.state;
  }
}
 
const conn = new Connection();
conn.handleEvent("connect");
console.log(conn.getState()); // => Output: Connected.Idle
conn.handleEvent("activity");
console.log(conn.getState()); // => Output: Connected.Active
conn.handleEvent("disconnect"); // => Parent transition fires from any substate
console.log(conn.getState()); // => Output: Disconnected

Key Takeaway: Hierarchical states reduce duplication by inheriting parent-level transitions. Substates specialize behavior while parent states define common transitions applicable to all substates.

Why It Matters: In production systems, hierarchical states reduce code duplication by 60-70%. When Spotify redesigned their playback FSM using hierarchical states, they eliminated 45 duplicate "error" transitions by defining error handling once at the parent Playing state level. All substates (Streaming, Buffering, Paused) inherited error handling automatically. This pattern is essential for complex domains where multiple substates share common exit conditions (authentication timeouts, network failures, user logouts).

Example 32: Implementing Parent State Transitions

Parent states define transitions that apply to all substates, eliminating duplicate transition logic.

Diagram

%% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73, Purple #CC78BC, Brown #CA9161
graph TD
    A["Connected (parent state)\ndisconnect transition applies to ALL substates"] -->|"contains"| B["Connected.Idle"]
    A -->|"contains"| C["Connected.Active"]
    A -->|"disconnect"| D["Disconnected"]
    D -->|"connect"| A
    B -->|"start_activity"| C
    C -->|"stop_activity"| B
 
    style A fill:#0173B2,stroke:#000,color:#fff
    style B fill:#DE8F05,stroke:#000,color:#000
    style C fill:#029E73,stroke:#000,color:#fff
    style D fill:#CC78BC,stroke:#000,color:#000

TypeScript Implementation:

// Hierarchical FSM: Parent transitions apply to all substates
type State = "Disconnected" | "Connected.Idle" | "Connected.Active"; // => Dot notation for substates
// => Type system ensures only valid states used
type Event = "connect" | "disconnect" | "activity" | "pause"; // => Four events
// => Events trigger state transitions
 
class NetworkConnection {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: State = "Disconnected"; // => Initial: Disconnected
  // => FSM begins execution in Disconnected state
 
  getCurrentState(): State {
    // => Query method: read current FSM state
    return this.state; // => Returns current state
  }
 
  handleEvent(event: Event): void {
    // => Event handler: main FSM dispatch method
    // Parent-level transition: disconnect from ANY Connected substate
    if (this.state.startsWith("Connected.") && event === "disconnect") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Event type check
      // => Combined (state, event) guard
      // => Check if in ANY Connected substate
      this.state = "Disconnected"; // => Exit all substates to Disconnected
      console.log("Disconnected (from any substate)"); // => Output for verification
      // => Chained method calls or nested operations
      // => Debug/audit output
      // => Log for observability
      return; // => Parent transition handled
    }
 
    // Substate-specific transitions
    if (this.state === "Disconnected" && event === "connect") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Event type check
      // => Combined (state, event) guard
      this.state = "Connected.Idle"; // => Enter parent state at default substate
      // => Parent: Connected, Substate: Idle
    } else if (this.state === "Connected.Idle" && event === "activity") {
      // => Logical AND: both conditions must be true
      this.state = "Connected.Active"; // => Idle → Active (within Connected)
    } else if (this.state === "Connected.Active" && event === "pause") {
      // => Logical AND: both conditions must be true
      this.state = "Connected.Idle"; // => Active → Idle (within Connected)
    } else {
      // => Fallback branch
      console.log(`Invalid transition: ${event} in ${this.state}`); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    }
  }
}
 
// Usage
const conn = new NetworkConnection(); // => state: "Disconnected"
conn.handleEvent("connect"); // => Disconnected → Connected.Idle
console.log(conn.getCurrentState()); // => Output: Connected.Idle
 
conn.handleEvent("activity"); // => Connected.Idle → Connected.Active
console.log(conn.getCurrentState()); // => Output: Connected.Active
 
conn.handleEvent("disconnect"); // => Parent transition: Any Connected → Disconnected
console.log(conn.getCurrentState()); // => Output: Disconnected

Key Takeaway: Parent transitions (disconnect) apply to all substates by checking state prefix. This eliminates duplicating disconnect logic for Idle and Active substates.

Why It Matters: Without parent transitions, you'd write disconnect logic multiple times (once per substate). At scale, this becomes unmaintainable - with many substates, you'd duplicate the transition many times. Shopping cart FSMs use parent-level "logout" transitions to eliminate duplicate logout handlers across cart substates (browsing, adding items, applying coupons, etc.). This guarantees consistent logout behavior regardless of which substate the user is in when they log out.

Example 33: Entry/Exit Actions in Hierarchical States

Entering/exiting hierarchical states triggers actions at both parent and substate levels, enabling cleanup and initialization.

TypeScript Implementation:

// Hierarchical states with entry/exit actions
type State = "Off" | "On.Starting" | "On.Running" | "On.Stopping"; // => Parent: On, Substates: Starting/Running/Stopping
// => Type system ensures only valid states used
type Event = "powerOn" | "started" | "stop" | "stopped" | "powerOff"; // => Five events
// => Events trigger state transitions
 
class Machine {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: State = "Off"; // => Initial: Off
  // => FSM begins execution in Off state
 
  getCurrentState(): State {
    // => Query method: read current FSM state
    return this.state; // => Returns state
  }
 
  private onEnterParent(): void {
    // => Extended state (data beyond FSM state)
    console.log("→ Entered On state (parent)"); // => Parent entry action
    // => Log for observability
    // => Example: Initialize resources
  }
 
  private onExitParent(): void {
    // => Extended state (data beyond FSM state)
    console.log("← Exited On state (parent)"); // => Parent exit action
    // => Log for observability
    // => Example: Release resources
  }
 
  handleEvent(event: Event): void {
    // => Event handler: main FSM dispatch method
    if (this.state === "Off" && event === "powerOn") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Event type check
      // => Combined (state, event) guard
      this.onEnterParent(); // => Entering parent state
      this.state = "On.Starting"; // => Enter at Starting substate
      console.log("  → Entered Starting substate"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    } else if (this.state === "On.Starting" && event === "started") {
      // => Logical AND: both conditions must be true
      console.log("  ← Exited Starting substate"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      this.state = "On.Running"; // => Starting → Running (within On)
      console.log("  → Entered Running substate"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    } else if (this.state === "On.Running" && event === "stop") {
      // => Logical AND: both conditions must be true
      console.log("  ← Exited Running substate"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      this.state = "On.Stopping"; // => Running → Stopping (within On)
      console.log("  → Entered Stopping substate"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    } else if (this.state === "On.Stopping" && event === "stopped") {
      // => Logical AND: both conditions must be true
      console.log("  ← Exited Stopping substate"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      this.onExitParent(); // => Exiting parent state
      this.state = "Off"; // => Exit parent to Off
    } else if (this.state.startsWith("On.") && event === "powerOff") {
      // => Logical AND: both conditions must be true
      console.log("  ← Exited current substate (emergency)"); // => Output for verification
      // => Chained method calls or nested operations
      // => Debug/audit output
      // => Log for observability
      this.onExitParent(); // => Parent exit action
      this.state = "Off"; // => Emergency exit from any On substate
    }
  }
}
 
// Usage
const machine = new Machine(); // => state: "Off"
machine.handleEvent("powerOn"); // => Off → On.Starting (parent entry + substate entry)
// => Output: → Entered On state (parent)
// =>         → Entered Starting substate
 
machine.handleEvent("started"); // => On.Starting → On.Running (substate transition)
// => Output: ← Exited Starting substate
// =>         → Entered Running substate
 
machine.handleEvent("powerOff"); // => Emergency exit: any On substate → Off
// => Output: ← Exited current substate (emergency)
// =>         ← Exited On state (parent)

Key Takeaway: Parent entry/exit actions execute when entering/leaving ANY substate of that parent. Substate transitions within the parent don't trigger parent entry/exit.

Why It Matters: Entry/exit actions at parent level prevent resource leaks. When Dropbox redesigned their sync engine FSM, they moved connection cleanup to parent-level exit actions. Previously, each of 8 sync substates duplicated cleanup logic - missing it in 2 substates caused 40K connection leaks/day. Parent exit actions guarantee cleanup runs regardless of which substate triggered the exit.

Example 34: Multiple Levels of Hierarchy

Hierarchical states can nest multiple levels deep, enabling fine-grained state organization.

stateDiagram-v2
    [*] --> Offline
 
    state Online {
        state Authenticated {
            [*] --> Browsing
            Browsing --> Purchasing: checkout
            Purchasing --> Browsing: cancel
        }
        [*] --> Guest
        Guest --> Authenticated: login
    }
 
    Offline --> Online: connect
    Online --> Offline: networkError
 
    classDef offlineState fill:#0173B2,stroke:#000,color:#fff
    classDef onlineState fill:#DE8F05,stroke:#000,color:#fff
    classDef guestState fill:#029E73,stroke:#000,color:#fff
    classDef authState fill:#CC78BC,stroke:#000,color:#fff
    classDef browseState fill:#CA9161,stroke:#000,color:#fff
 
    class Offline offlineState
    class Online onlineState
    class Guest guestState
    class Authenticated authState
    class Browsing,Purchasing browseState

TypeScript Implementation:

// Multi-level hierarchical FSM
type State = "Offline" | "Online.Guest" | "Online.Authenticated.Browsing" | "Online.Authenticated.Purchasing"; // => Three hierarchy levels
// => Type system ensures only valid states used
type Event = "connect" | "login" | "checkout" | "cancel" | "networkError"; // => Five events
// => Events trigger state transitions
 
class ShoppingApp {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: State = "Offline"; // => Initial: Offline
  // => FSM begins execution in Offline state
 
  getCurrentState(): State {
    // => Query method: read current FSM state
    return this.state; // => Returns state
  }
 
  handleEvent(event: Event): void {
    // => Event handler: main FSM dispatch method
    // Level 1 parent transition: networkError exits ALL Online substates
    if (this.state.startsWith("Online.") && event === "networkError") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Event type check
      // => Combined (state, event) guard
      // => Check if in any Online substate (any level deep)
      this.state = "Offline"; // => Exit all levels to Offline
      console.log("Network error: Offline"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      return;
    }
 
    // Regular transitions
    if (this.state === "Offline" && event === "connect") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Event type check
      // => Combined (state, event) guard
      this.state = "Online.Guest"; // => Enter Online parent at Guest substate
    } else if (this.state === "Online.Guest" && event === "login") {
      // => Logical AND: both conditions must be true
      this.state = "Online.Authenticated.Browsing"; // => Enter Authenticated parent at Browsing
      // => Now two levels deep: Online → Authenticated → Browsing
    } else if (this.state === "Online.Authenticated.Browsing" && event === "checkout") {
      // => Logical AND: both conditions must be true
      this.state = "Online.Authenticated.Purchasing"; // => Browsing → Purchasing (within Authenticated)
    } else if (this.state === "Online.Authenticated.Purchasing" && event === "cancel") {
      // => Logical AND: both conditions must be true
      this.state = "Online.Authenticated.Browsing"; // => Purchasing → Browsing
    } else {
      // => Fallback branch
      console.log(`Invalid transition: ${event} in ${this.state}`); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    }
  }
}
 
// Usage
const app = new ShoppingApp(); // => state: "Offline"
app.handleEvent("connect"); // => Offline → Online.Guest
console.log(app.getCurrentState()); // => Output: Online.Guest
 
app.handleEvent("login"); // => Online.Guest → Online.Authenticated.Browsing (two levels deep)
console.log(app.getCurrentState()); // => Output: Online.Authenticated.Browsing
 
app.handleEvent("checkout"); // => Browsing → Purchasing (within Authenticated)
console.log(app.getCurrentState()); // => Output: Online.Authenticated.Purchasing
 
app.handleEvent("networkError"); // => Parent transition: exits ALL levels to Offline
console.log(app.getCurrentState()); // => Output: Offline

Key Takeaway: Multi-level hierarchies enable fine-grained state organization. Parent transitions at any level apply to all descendant substates, regardless of depth.

Why It Matters: Deep hierarchies model complex domains without explosion of duplicate transitions. Video player FSMs use multi-level hierarchy (Device → Network → Playback → Quality). A single "logout" transition at Device level handles logout from many descendant states (combinations of network conditions, playback states, quality settings). Without hierarchy, duplicate logout handlers would be needed for each state.

Example 35: Default Substate Entry

When entering a parent state, FSMs can specify which substate to enter by default, enabling predictable initialization.

TypeScript Implementation:

// Hierarchical FSM with default substate entry
type State = "Stopped" | "Playing.Loading" | "Playing.Buffering" | "Playing.Active"; // => Parent: Playing
// => Type system ensures only valid states used
type Event = "play" | "loaded" | "buffered" | "pause"; // => Four events
// => Events trigger state transitions
 
interface StateConfig {
  // => Type declaration defines structure
  defaultSubstate?: State; // => Default substate when entering parent
}
 
class VideoPlayer {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: State = "Stopped"; // => Initial: Stopped
  // => FSM begins execution in Stopped state
  private readonly stateConfig: Record<string, StateConfig> = {
    // => State field: stores current FSM state privately
    // => Extended state (data beyond FSM state)
    // => Initialized alongside FSM state
    Playing: { defaultSubstate: "Playing.Loading" }, // => Playing defaults to Loading substate
  };
 
  getCurrentState(): State {
    // => Query method: read current FSM state
    return this.state; // => Returns state
  }
 
  handleEvent(event: Event): void {
    // => Event handler: main FSM dispatch method
    if (this.state === "Stopped" && event === "play") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Event type check
      // => Combined (state, event) guard
      const defaultSubstate = this.stateConfig["Playing"].defaultSubstate!; // => State variable initialization
      // => Initialize defaultSubstate
      this.state = defaultSubstate; // => Enter Playing at default substate (Loading)
      console.log(`Entered Playing at default: ${this.state}`); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    } else if (this.state === "Playing.Loading" && event === "loaded") {
      // => Logical AND: both conditions must be true
      this.state = "Playing.Buffering"; // => Loading → Buffering
    } else if (this.state === "Playing.Buffering" && event === "buffered") {
      // => Logical AND: both conditions must be true
      this.state = "Playing.Active"; // => Buffering → Active
    } else if (this.state.startsWith("Playing.") && event === "pause") {
      // => Logical AND: both conditions must be true
      this.state = "Stopped"; // => Exit Playing from any substate
    } else {
      // => Fallback branch
      console.log(`Invalid transition: ${event} in ${this.state}`); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    }
  }
}
 
// Usage
const player = new VideoPlayer(); // => state: "Stopped"
player.handleEvent("play"); // => Stopped → Playing.Loading (default substate)
// => Output: Entered Playing at default: Playing.Loading
 
console.log(player.getCurrentState()); // => Output: Playing.Loading
 
player.handleEvent("loaded"); // => Playing.Loading → Playing.Buffering
console.log(player.getCurrentState()); // => Output: Playing.Buffering

Key Takeaway: Default substates ensure consistent entry points when transitioning to parent states. Configuration-driven defaults make entry behavior explicit.

Why It Matters: Default substates prevent ambiguous entry. Without defaults, "resume playback" could enter Playing at any of 5 substates (Loading/Buffering/Active/Paused/Seeking), causing inconsistent behavior. YouTube's player FSM uses default substates to guarantee playback always starts at Loading state, ensuring proper initialization sequence (load → buffer → play) regardless of how Playing state is entered.

Composite States (Examples 36-40)

Example 36: What are Composite States?

Composite states contain concurrent regions (orthogonal regions) that execute independently and simultaneously. Unlike hierarchical states (where only one substate is active), composite states have multiple active substates at the same time.

stateDiagram-v2
    [*] --> System
 
    state System {
        state Audio {
            [*] --> Muted
            Muted --> Playing: unmute
            Playing --> Muted: mute
        }
        --
        state Video {
            [*] --> Paused
            Paused --> Streaming: play
            Streaming --> Paused: pause
        }
    }
 
    classDef audioState fill:#0173B2,stroke:#000,color:#fff
    classDef videoState fill:#DE8F05,stroke:#000,color:#fff
 
    class Audio,Muted,Playing audioState
    class Video,Paused,Streaming videoState

Key Concept: System is a composite state with TWO concurrent regions (Audio and Video). Both regions are active simultaneously - you can be "Muted + Streaming" or "Playing + Paused" at the same time. The -- notation in Mermaid separates orthogonal regions.

Minimal TypeScript Example:

// Composite state: two concurrent regions tracked independently
type AudioRegion = "Muted" | "Playing"; // => Audio region: independent state
type VideoRegion = "Paused" | "Streaming"; // => Video region: independent state
type MediaEvent = "mute" | "unmute" | "play" | "pause";
 
class CompositeMediaSystem {
  private audio: AudioRegion = "Muted"; // => Region 1 initial state
  private video: VideoRegion = "Paused"; // => Region 2 initial state (independent of audio)
 
  handleEvent(event: MediaEvent): void {
    // Each event targets one region; the other region is unaffected
    if (event === "unmute")
      this.audio = "Playing"; // => Audio region: Muted → Playing
    else if (event === "mute")
      this.audio = "Muted"; // => Audio region: Playing → Muted
    else if (event === "play")
      this.video = "Streaming"; // => Video region: Paused → Streaming
    else if (event === "pause") this.video = "Paused"; // => Video region: Streaming → Paused
  }
 
  getState() {
    return { audio: this.audio, video: this.video };
  } // => Both regions active simultaneously
}
 
const media = new CompositeMediaSystem();
media.handleEvent("play");
console.log(media.getState()); // => Output: { audio: 'Muted', video: 'Streaming' }
media.handleEvent("unmute");
console.log(media.getState()); // => Output: { audio: 'Playing', video: 'Streaming' }

Key Takeaway: Composite states enable independent parallel state machines within a single parent state. Each region maintains its own state independently.

Why It Matters: Composite states model real-world systems where multiple aspects operate independently. A video conferencing app has composite state for Call (Audio region: muted/unmuted, Video region: on/off, Screen region: shared/not-shared). Without composite states, you'd need 2³=8 separate states for all combinations. Zoom's FSM uses composite states to model 5 independent regions (audio, video, screen share, recording, reactions), avoiding 2⁵=32 combination states.

Example 37: Implementing Composite States

Composite states track multiple independent state variables, one per orthogonal region.

TypeScript Implementation:

// Composite FSM: Two concurrent regions (Audio + Video)
type AudioState = "Muted" | "Playing"; // => Audio region states
// => Type system ensures only valid states used
type VideoState = "Paused" | "Streaming"; // => Video region states
// => Type system ensures only valid states used
type Event = "mute" | "unmute" | "play" | "pause"; // => Events target specific regions
// => Events trigger state transitions
 
class MediaPlayer {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private audioState: AudioState = "Muted"; // => Region 1: Audio
  // => Initialized alongside FSM state
  private videoState: VideoState = "Paused"; // => Region 2: Video
  // => Initialized alongside FSM state
 
  getCurrentState(): { audio: AudioState; video: VideoState } {
    // => Query method: read current FSM state
    return { audio: this.audioState, video: this.videoState }; // => Returns both region states
  }
 
  handleEvent(event: Event): void {
    // => Event handler: main FSM dispatch method
    // Audio region transitions
    if (event === "unmute" && this.audioState === "Muted") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Event type check
      this.audioState = "Playing"; // => Muted → Playing (audio region only)
    } else if (event === "mute" && this.audioState === "Playing") {
      // => Logical AND: both conditions must be true
      this.audioState = "Muted"; // => Playing → Muted (audio region only)
    }
    // Video region transitions
    else if (event === "play" && this.videoState === "Paused") {
      // => Alternative conditional branch
      // => Logical AND: both conditions must be true
      this.videoState = "Streaming"; // => Paused → Streaming (video region only)
    } else if (event === "pause" && this.videoState === "Streaming") {
      // => Logical AND: both conditions must be true
      this.videoState = "Paused"; // => Streaming → Paused (video region only)
    } else {
      // => Fallback branch
      console.log(`Invalid transition: ${event} in audio=${this.audioState}, video=${this.videoState}`); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    }
  }
}
 
// Usage
const player = new MediaPlayer(); // => audio: Muted, video: Paused
console.log(player.getCurrentState()); // => Output: { audio: 'Muted', video: 'Paused' }
 
player.handleEvent("play"); // => Video region: Paused → Streaming (audio unchanged)
console.log(player.getCurrentState()); // => Output: { audio: 'Muted', video: 'Streaming' }
 
player.handleEvent("unmute"); // => Audio region: Muted → Playing (video unchanged)
console.log(player.getCurrentState()); // => Output: { audio: 'Playing', video: 'Streaming' }
 
player.handleEvent("pause"); // => Video region: Streaming → Paused (audio unchanged)
console.log(player.getCurrentState()); // => Output: { audio: 'Playing', video: 'Paused' }

Key Takeaway: Composite states use separate state variables for each orthogonal region. Events affect only their target region, leaving other regions unchanged.

Why It Matters: Independent state tracking prevents combinatorial explosion. Without composite states, the example above needs 4 states (Muted+Paused, Muted+Streaming, Playing+Paused, Playing+Streaming). Add a third region (subtitles: on/off) and you need 8 states. With composite states, you track 3 independent variables instead of 8 combination states. This exponential reduction in state count makes the FSM comprehensible and testable, with each region tested independently rather than testing all state combinations together.

Example 38: Cross-Region Synchronization

Sometimes regions need to coordinate - events in one region can trigger transitions in another region.

TypeScript Implementation:

// Composite FSM with cross-region coordination
type PowerState = "On" | "Off"; // => Power region
// => Type system ensures only valid states used
type DisplayState = "Showing" | "Hidden"; // => Display region
// => Type system ensures only valid states used
type Event = "powerOn" | "powerOff" | "show" | "hide"; // => Events
// => Events trigger state transitions
 
class Device {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private powerState: PowerState = "Off"; // => Region 1: Power
  // => Initialized alongside FSM state
  private displayState: DisplayState = "Hidden"; // => Region 2: Display
  // => Initialized alongside FSM state
 
  getCurrentState(): { power: PowerState; display: DisplayState } {
    // => Query method: read current FSM state
    return { power: this.powerState, display: this.displayState }; // => Returns value to caller
    // => Return computed result
  }
 
  handleEvent(event: Event): void {
    // => Event handler: main FSM dispatch method
    if (event === "powerOn" && this.powerState === "Off") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Event type check
      this.powerState = "On"; // => Power: Off → On
      this.displayState = "Showing"; // => Cross-region: Force Display to Showing
      console.log("Power on: Display forced to Showing"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    } else if (event === "powerOff" && this.powerState === "On") {
      // => Logical AND: both conditions must be true
      this.powerState = "Off"; // => Power: On → Off
      this.displayState = "Hidden"; // => Cross-region: Force Display to Hidden
      console.log("Power off: Display forced to Hidden"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    } else if (event === "show" && this.powerState === "On" && this.displayState === "Hidden") {
      // => Logical AND: both conditions must be true
      this.displayState = "Showing"; // => Display: Hidden → Showing (only if powered on)
    } else if (event === "hide" && this.powerState === "On" && this.displayState === "Showing") {
      // => Logical AND: both conditions must be true
      this.displayState = "Hidden"; // => Display: Showing → Hidden (only if powered on)
    } else {
      // => Fallback branch
      console.log(`Invalid transition: ${event} in power=${this.powerState}, display=${this.displayState}`); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    }
  }
}
 
// Usage
const device = new Device(); // => power: Off, display: Hidden
console.log(device.getCurrentState()); // => Output: { power: 'Off', display: 'Hidden' }
 
device.handleEvent("powerOn"); // => Power On → forces Display to Showing
// => Output: Power on: Display forced to Showing
console.log(device.getCurrentState()); // => Output: { power: 'On', display: 'Showing' }
 
device.handleEvent("hide"); // => Display: Showing → Hidden (independent)
console.log(device.getCurrentState()); // => Output: { power: 'On', display: 'Hidden' }
 
device.handleEvent("powerOff"); // => Power Off → forces Display to Hidden
// => Output: Power off: Display forced to Hidden
console.log(device.getCurrentState()); // => Output: { power: 'Off', display: 'Hidden' }

Key Takeaway: Cross-region synchronization enforces dependencies between orthogonal regions. Power state changes force display state changes to maintain consistency.

Why It Matters: Cross-region coordination prevents invalid combinations. A device can't show display while powered off. Vehicle FSMs use cross-region sync: when Drive region enters "Park" state, it forces Safety region to "Doors Unlocked" state. This prevents the invalid combination "Park + Doors Locked" which would trap passengers. Cross-region constraints also serve as living documentation of the system's safety invariants, making safety requirements verifiable through automated tests.

Example 39: Join Synchronization in Composite States

Join transitions require multiple regions to reach specific states before triggering a combined transition.

TypeScript Implementation:

// Composite FSM with join synchronization
type AuthState = "Unauthenticated" | "Authenticated"; // => Auth region
// => Type system ensures only valid states used
type DataState = "NotLoaded" | "Loaded"; // => Data region
// => Type system ensures only valid states used
type CombinedState = "NotReady" | "Ready"; // => Combined state after join
// => Type system ensures only valid states used
type Event = "login" | "loadData" | "logout" | "clearData"; // => Events
// => Events trigger state transitions
 
class Application {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private authState: AuthState = "Unauthenticated"; // => Region 1: Auth
  // => Initialized alongside FSM state
  private dataState: DataState = "NotLoaded"; // => Region 2: Data
  // => Initialized alongside FSM state
  private combinedState: CombinedState = "NotReady"; // => Derived state from regions
  // => Initialized alongside FSM state
 
  getCurrentState(): { auth: AuthState; data: DataState; combined: CombinedState } {
    // => Query method: read current FSM state
    return { auth: this.authState, data: this.dataState, combined: this.combinedState }; // => Returns value to caller
    // => Return computed result
  }
 
  private checkReadiness(): void {
    // => Extended state (data beyond FSM state)
    // Join condition: BOTH regions must be in specific states
    if (this.authState === "Authenticated" && this.dataState === "Loaded") {
      // => Conditional branch
      // => Logical AND: both conditions must be true
      // => Conditional check
      // => Branch execution based on condition
      this.combinedState = "Ready"; // => Join: Auth+Data ready → App ready
      console.log("Join: Application ready (auth + data complete)"); // => Output for verification
      // => Chained method calls or nested operations
      // => Debug/audit output
      // => Log for observability
    } else {
      // => Fallback branch
      this.combinedState = "NotReady"; // => Either region not ready → App not ready
    }
  }
 
  handleEvent(event: Event): void {
    // => Event handler: main FSM dispatch method
    if (event === "login" && this.authState === "Unauthenticated") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Event type check
      this.authState = "Authenticated"; // => Auth: Unauthenticated → Authenticated
      this.checkReadiness(); // => Check if join condition met
    } else if (event === "loadData" && this.dataState === "NotLoaded") {
      // => Logical AND: both conditions must be true
      this.dataState = "Loaded"; // => Data: NotLoaded → Loaded
      this.checkReadiness(); // => Check if join condition met
    } else if (event === "logout") {
      this.authState = "Unauthenticated"; // => Auth: reset
      this.checkReadiness(); // => Combined becomes NotReady
    } else if (event === "clearData") {
      this.dataState = "NotLoaded"; // => Data: reset
      this.checkReadiness(); // => Combined becomes NotReady
    }
  }
}
 
// Usage
const app = new Application(); // => auth: Unauthenticated, data: NotLoaded, combined: NotReady
console.log(app.getCurrentState()); // => Output for verification
// => Chained method calls or nested operations
// => Query method: read current FSM state
// => Output: { auth: 'Unauthenticated', data: 'NotLoaded', combined: 'NotReady' }
 
app.handleEvent("login"); // => Auth ready, but data not ready yet
console.log(app.getCurrentState()); // => Output for verification
// => Chained method calls or nested operations
// => Query method: read current FSM state
// => Output: { auth: 'Authenticated', data: 'NotLoaded', combined: 'NotReady' }
 
app.handleEvent("loadData"); // => Data ready → JOIN condition met!
// => Output: Join: Application ready (auth + data complete)
console.log(app.getCurrentState()); // => Output for verification
// => Chained method calls or nested operations
// => Query method: read current FSM state
// => Output: { auth: 'Authenticated', data: 'Loaded', combined: 'Ready' }
 
app.handleEvent("logout"); // => Auth reset → Join broken
console.log(app.getCurrentState()); // => Output for verification
// => Chained method calls or nested operations
// => Query method: read current FSM state
// => Output: { auth: 'Unauthenticated', data: 'Loaded', combined: 'NotReady' }

Key Takeaway: Join synchronization waits for multiple regions to reach required states before transitioning to a combined state. If any region exits its required state, the join breaks.

Why It Matters: Join synchronization models AND conditions in parallel workflows. Food delivery systems require multiple parallel processes to complete before "Order Ready" state: (1) Restaurant prepares food, (2) Driver arrives at restaurant, (3) Payment authorized. If any process fails, order isn't ready. Join states prevent premature transitions when only some conditions are met.

Example 40: Fork Synchronization - Splitting into Parallel Regions

Fork transitions split a single state into multiple concurrent regions, enabling parallel execution.

TypeScript Implementation:

// Fork: Single state splits into concurrent regions
type SingleState = "Idle"; // => Initial single state
// => Type system ensures only valid states used
type WorkerAState = "ProcessingA" | "DoneA"; // => Region A after fork
// => Type system ensures only valid states used
type WorkerBState = "ProcessingB" | "DoneB"; // => Region B after fork
// => Type system ensures only valid states used
type Event = "start" | "completeA" | "completeB"; // => Events
// => Events trigger state transitions
 
class ParallelProcessor {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: "single" | "forked" = "single"; // => Mode: single state or forked regions
  // => FSM begins execution in single state
  private singleState: SingleState | null = "Idle"; // => State when in single mode
  // => Initialized alongside FSM state
  private workerA: WorkerAState | null = null; // => Region A (null when not forked)
  // => Initialized alongside FSM state
  private workerB: WorkerBState | null = null; // => Region B (null when not forked)
  // => Initialized alongside FSM state
 
  getCurrentState(): any {
    // => Query method: read current FSM state
    if (this.state === "single") {
      // => State-based guard condition
      // => Guard condition: check current state is single
      // => Only execute if condition true
      return { mode: "single", state: this.singleState }; // => Single state active
    } else {
      // => Fallback branch
      return { mode: "forked", workerA: this.workerA, workerB: this.workerB }; // => Both regions active
    }
  }
 
  handleEvent(event: Event): void {
    // => Event handler: main FSM dispatch method
    if (event === "start" && this.state === "single" && this.singleState === "Idle") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Event type check
      // => Combined (state, event) guard
      // FORK: Split into two concurrent regions
      this.state = "forked"; // => Enter forked mode
      this.singleState = null; // => Exit single state
      this.workerA = "ProcessingA"; // => Region A starts
      this.workerB = "ProcessingB"; // => Region B starts
      console.log("Fork: Idle → ProcessingA + ProcessingB (parallel)"); // => Output for verification
      // => Chained method calls or nested operations
      // => Debug/audit output
      // => Log for observability
    } else if (event === "completeA" && this.state === "forked" && this.workerA === "ProcessingA") {
      // => Logical AND: both conditions must be true
      this.workerA = "DoneA"; // => Region A completes
      console.log("Worker A completed"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      this.checkCompletion(); // => Check if both regions done
    } else if (event === "completeB" && this.state === "forked" && this.workerB === "ProcessingB") {
      // => Logical AND: both conditions must be true
      this.workerB = "DoneB"; // => Region B completes
      console.log("Worker B completed"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      this.checkCompletion(); // => Check if both regions done
    }
  }
 
  private checkCompletion(): void {
    // => Extended state (data beyond FSM state)
    if (this.workerA === "DoneA" && this.workerB === "DoneB") {
      // => Conditional branch
      // => Logical AND: both conditions must be true
      // => Conditional check
      // => Branch execution based on condition
      // JOIN: Both regions complete → merge back to single state
      this.state = "single"; // => Exit forked mode
      this.singleState = "Idle"; // => Return to Idle
      this.workerA = null; // => Clear region A
      this.workerB = null; // => Clear region B
      console.log("Join: Both workers done → Idle"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    }
  }
}
 
// Usage
const processor = new ParallelProcessor(); // => mode: single, state: Idle
console.log(processor.getCurrentState()); // => Output: { mode: 'single', state: 'Idle' }
 
processor.handleEvent("start"); // => Fork: Idle → ProcessingA + ProcessingB
// => Output: Fork: Idle → ProcessingA + ProcessingB (parallel)
console.log(processor.getCurrentState()); // => Output for verification
// => Chained method calls or nested operations
// => Query method: read current FSM state
// => Output: { mode: 'forked', workerA: 'ProcessingA', workerB: 'ProcessingB' }
 
processor.handleEvent("completeA"); // => Worker A done (B still processing)
// => Output: Worker A completed
console.log(processor.getCurrentState()); // => Output for verification
// => Chained method calls or nested operations
// => Query method: read current FSM state
// => Output: { mode: 'forked', workerA: 'DoneA', workerB: 'ProcessingB' }
 
processor.handleEvent("completeB"); // => Worker B done → JOIN back to Idle
// => Output: Worker B completed
// =>         Join: Both workers done → Idle
console.log(processor.getCurrentState()); // => Output for verification
// => Chained method calls or nested operations
// => Query method: read current FSM state
// => Output: { mode: 'single', state: 'Idle' }

Key Takeaway: Fork transitions split a single state into multiple concurrent regions. Join transitions merge regions back into a single state when all regions reach terminal states.

Why It Matters: Fork-join models MapReduce and parallel processing patterns. Search query processing forks into many parallel regions (each searching a data shard), then joins results when all regions complete. Without fork-join FSM, coordinating parallel work and merging results becomes error-prone - missing a completion signal means join never triggers. Fork-join also provides natural timeout handling: if any parallel region exceeds its time budget, the parent FSM can transition to a timeout state.

Parallel States (Examples 41-44)

Example 41: Parallel State Regions

Parallel states (orthogonal states) enable multiple independent state machines to execute simultaneously within a parent state.

stateDiagram-v2
    [*] --> Active
 
    state Active {
        state Connectivity {
            [*] --> Online
            Online --> Offline: disconnect
            Offline --> Online: reconnect
        }
        --
        state Authentication {
            [*] --> LoggedOut
            LoggedOut --> LoggedIn: login
            LoggedIn --> LoggedOut: logout
        }
    }
 
    classDef connectState fill:#0173B2,stroke:#000,color:#fff
    classDef authState fill:#DE8F05,stroke:#000,color:#fff
 
    class Connectivity,Online,Offline connectState
    class Authentication,LoggedOut,LoggedIn authState

TypeScript Implementation:

// Parallel regions: Connectivity + Authentication
type ConnectivityState = "Online" | "Offline"; // => Region 1
// => Type system ensures only valid states used
type AuthState = "LoggedOut" | "LoggedIn"; // => Region 2
// => Type system ensures only valid states used
type Event = "disconnect" | "reconnect" | "login" | "logout"; // => Events for both regions
// => Events trigger state transitions
 
class System {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private connectivity: ConnectivityState = "Online"; // => Region 1: Connectivity
  // => Initialized alongside FSM state
  private auth: AuthState = "LoggedOut"; // => Region 2: Authentication
  // => Initialized alongside FSM state
 
  getCurrentState(): { connectivity: ConnectivityState; auth: AuthState } {
    // => Query method: read current FSM state
    return { connectivity: this.connectivity, auth: this.auth }; // => Returns value to caller
    // => Return computed result
  }
 
  handleEvent(event: Event): void {
    // => Event handler: main FSM dispatch method
    // Connectivity region
    if (event === "disconnect" && this.connectivity === "Online") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Event type check
      this.connectivity = "Offline"; // => Online → Offline
    } else if (event === "reconnect" && this.connectivity === "Offline") {
      // => Logical AND: both conditions must be true
      this.connectivity = "Online"; // => Offline → Online
    }
    // Authentication region (independent of connectivity)
    else if (event === "login" && this.auth === "LoggedOut") {
      // => Alternative conditional branch
      // => Logical AND: both conditions must be true
      this.auth = "LoggedIn"; // => LoggedOut → LoggedIn
      // => Can login while Offline (credential caching)
    } else if (event === "logout" && this.auth === "LoggedIn") {
      // => Logical AND: both conditions must be true
      this.auth = "LoggedOut"; // => LoggedIn → LoggedOut
    }
  }
}
 
// Usage
const system = new System(); // => connectivity: Online, auth: LoggedOut
console.log(system.getCurrentState()); // => Output: { connectivity: 'Online', auth: 'LoggedOut' }
 
system.handleEvent("login"); // => Auth: LoggedOut → LoggedIn (connectivity unchanged)
console.log(system.getCurrentState()); // => Output: { connectivity: 'Online', auth: 'LoggedIn' }
 
system.handleEvent("disconnect"); // => Connectivity: Online → Offline (auth unchanged)
console.log(system.getCurrentState()); // => Output: { connectivity: 'Offline', auth: 'LoggedIn' }
// => Can be Offline + LoggedIn simultaneously

Key Takeaway: Parallel regions execute independently. Changes in one region don't affect other regions unless explicitly coordinated.

Why It Matters: Parallel regions prevent false dependencies. An app can be "Offline + LoggedIn" - network connectivity and authentication are orthogonal concerns. Slack's FSM uses parallel regions for 4 independent aspects: network (online/offline), auth (logged in/out), workspace (selected/none), notifications (enabled/disabled). Without parallel states, combinations explode to 2⁴=16 states. Parallel regions also enable each dimension to evolve independently—adding a new network state doesn't require modifying the authentication or notification regions.

Example 42: Broadcast Events to Parallel Regions

Single events can trigger transitions in multiple parallel regions simultaneously.

TypeScript Implementation:

stateDiagram-v2
    state "Broadcast Event FSM" as BroadcastFSM {
        state "Region 1" as R1 {
            R1_Idle --> R1_Active: activate
            R1_Active --> R1_Idle: deactivate
            R1_Idle --> R1_Idle: reset
            R1_Active --> R1_Idle: reset
        }
        --
        state "Region 2" as R2 {
            R2_Idle --> R2_Active: activate
            R2_Active --> R2_Idle: deactivate
            R2_Idle --> R2_Idle: reset
            R2_Active --> R2_Idle: reset
        }
    }
    note right of BroadcastFSM
        Single event triggers
        both regions simultaneously
    end note
// Broadcast event affects multiple regions
type Region1State = "R1_Idle" | "R1_Active"; // => Region 1 states
// => Type system ensures only valid states used
type Region2State = "R2_Idle" | "R2_Active"; // => Region 2 states
// => Type system ensures only valid states used
type Event = "activate" | "deactivate" | "reset"; // => Events
// => Events trigger state transitions
 
class MultiRegionSystem {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private region1: Region1State = "R1_Idle"; // => Region 1
  // => Initialized alongside FSM state
  private region2: Region2State = "R2_Idle"; // => Region 2
  // => Initialized alongside FSM state
 
  getCurrentState(): { region1: Region1State; region2: Region2State } {
    // => Query method: read current FSM state
    return { region1: this.region1, region2: this.region2 }; // => Returns value to caller
    // => Return computed result
  }
 
  handleEvent(event: Event): void {
    // => Event handler: main FSM dispatch method
    if (event === "activate") {
      // => Event type guard condition
      // => Event type check
      // Broadcast: activates BOTH regions
      if (this.region1 === "R1_Idle") {
        // => Conditional branch
        // => Conditional check
        // => Branch execution based on condition
        this.region1 = "R1_Active"; // => Region 1: Idle → Active
      }
      if (this.region2 === "R2_Idle") {
        // => Conditional branch
        // => Conditional check
        // => Branch execution based on condition
        this.region2 = "R2_Active"; // => Region 2: Idle → Active
      }
      console.log("Broadcast: activated both regions"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    } else if (event === "deactivate") {
      // Broadcast: deactivates BOTH regions
      if (this.region1 === "R1_Active") {
        // => Conditional branch
        // => Conditional check
        // => Branch execution based on condition
        this.region1 = "R1_Idle"; // => Region 1: Active → Idle
      }
      if (this.region2 === "R2_Active") {
        // => Conditional branch
        // => Conditional check
        // => Branch execution based on condition
        this.region2 = "R2_Idle"; // => Region 2: Active → Idle
      }
      console.log("Broadcast: deactivated both regions"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    } else if (event === "reset") {
      // => Accessor: provides controlled state access
      // Broadcast: resets ALL regions to initial states
      this.region1 = "R1_Idle"; // => Region 1 reset
      this.region2 = "R2_Idle"; // => Region 2 reset
      console.log("Broadcast: reset all regions"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    }
  }
}
 
// Usage
const multiSystem = new MultiRegionSystem(); // => both regions: Idle
console.log(multiSystem.getCurrentState()); // => Output for verification
// => Chained method calls or nested operations
// => Query method: read current FSM state
// => Output: { region1: 'R1_Idle', region2: 'R2_Idle' }
 
multiSystem.handleEvent("activate"); // => Broadcast: both regions activate
// => Output: Broadcast: activated both regions
console.log(multiSystem.getCurrentState()); // => Output for verification
// => Chained method calls or nested operations
// => Query method: read current FSM state
// => Output: { region1: 'R1_Active', region2: 'R2_Active' }
 
multiSystem.handleEvent("reset"); // => Broadcast: reset all regions
// => Output: Broadcast: reset all regions
console.log(multiSystem.getCurrentState()); // => Output for verification
// => Chained method calls or nested operations
// => Query method: read current FSM state
// => Output: { region1: 'R1_Idle', region2: 'R2_Idle' }

Key Takeaway: Broadcast events enable coordinated transitions across parallel regions. Single event updates multiple regions atomically.

Why It Matters: Broadcast events simplify system-wide operations. When a mobile app receives "low battery" event, it should broadcast to all regions: disable GPS region, reduce screen brightness region, pause background sync region. Without broadcast, you'd send 3 separate events, risking partial execution if one fails. Broadcast events also ensure atomicity across regions: all affected regions receive the same event in the same processing cycle, preventing inconsistent intermediate states.

Example 43: Conditional Parallel Region Activation

Parallel regions can be conditionally activated based on configuration or runtime state.

TypeScript Implementation:

// Conditionally activate parallel regions
type FeatureState = "Enabled" | "Disabled" | null; // => null = region not active
// => Type system ensures only valid states used
type Event = "enableFeatureA" | "enableFeatureB" | "disableFeatureA" | "disableFeatureB"; // => Type declaration defines structure
// => Defines event alphabet for FSM
// => Events trigger state transitions
 
class ConfigurableSystem {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private featureA: FeatureState = null; // => Region A: initially inactive
  // => Initialized alongside FSM state
  private featureB: FeatureState = null; // => Region B: initially inactive
  // => Initialized alongside FSM state
 
  getCurrentState(): { featureA: FeatureState; featureB: FeatureState } {
    // => Query method: read current FSM state
    return { featureA: this.featureA, featureB: this.featureB }; // => Returns value to caller
    // => Return computed result
  }
 
  handleEvent(event: Event): void {
    // => Event handler: main FSM dispatch method
    if (event === "enableFeatureA") {
      // => Event type guard condition
      // => Event type check
      if (this.featureA === null) {
        // => Conditional branch
        // => Conditional check
        // => Branch execution based on condition
        this.featureA = "Disabled"; // => Activate region A at initial state
        console.log("Feature A region activated"); // => Output for verification
        // => Debug/audit output
        // => Log for observability
      }
      if (this.featureA === "Disabled") {
        // => Conditional branch
        // => Conditional check
        // => Branch execution based on condition
        this.featureA = "Enabled"; // => Disabled → Enabled
      }
    } else if (event === "disableFeatureA") {
      if (this.featureA === "Enabled") {
        // => Conditional branch
        // => Conditional check
        // => Branch execution based on condition
        this.featureA = "Disabled"; // => Enabled → Disabled
      }
    } else if (event === "enableFeatureB") {
      if (this.featureB === null) {
        // => Conditional branch
        // => Conditional check
        // => Branch execution based on condition
        this.featureB = "Disabled"; // => Activate region B
        console.log("Feature B region activated"); // => Output for verification
        // => Debug/audit output
        // => Log for observability
      }
      if (this.featureB === "Disabled") {
        // => Conditional branch
        // => Conditional check
        // => Branch execution based on condition
        this.featureB = "Enabled";
      }
    } else if (event === "disableFeatureB") {
      if (this.featureB === "Enabled") {
        // => Conditional branch
        // => Conditional check
        // => Branch execution based on condition
        this.featureB = "Disabled";
      }
    }
  }
}
 
// Usage
const configSystem = new ConfigurableSystem(); // => both regions: null (inactive)
console.log(configSystem.getCurrentState()); // => Output for verification
// => Chained method calls or nested operations
// => Query method: read current FSM state
// => Output: { featureA: null, featureB: null }
 
configSystem.handleEvent("enableFeatureA"); // => Activate region A
// => Output: Feature A region activated
console.log(configSystem.getCurrentState()); // => Output for verification
// => Chained method calls or nested operations
// => Query method: read current FSM state
// => Output: { featureA: 'Enabled', featureB: null }
 
configSystem.handleEvent("enableFeatureB"); // => Activate region B
// => Output: Feature B region activated
console.log(configSystem.getCurrentState()); // => Output for verification
// => Chained method calls or nested operations
// => Query method: read current FSM state
// => Output: { featureA: 'Enabled', featureB: 'Enabled' }

Key Takeaway: Parallel regions can be dynamically activated/deactivated based on configuration or feature flags. Null state indicates inactive region.

Why It Matters: Conditional activation enables feature flags and A/B testing. FSMs conditionally activate parallel regions for experimental features - some users get experimental regions activated, others keep it null. This prevents loading unused code and simplifies state management for users without the feature. Conditional region activation enables safe incremental feature rollouts by controlling which users have which regions activated through configuration, without code changes.

Example 44: Error Handling Across Parallel Regions

Errors in one parallel region can propagate to other regions or be isolated based on error handling strategy.

TypeScript Implementation:

// Error handling: isolated vs. propagating
type WorkerState = "Working" | "Error" | "Stopped"; // => Worker region states
// => Type system ensures only valid states used
type MonitorState = "Monitoring" | "AlertSent"; // => Monitor region states
// => Type system ensures only valid states used
type Event = "work" | "error" | "acknowledge" | "stop"; // => Events
// => Events trigger state transitions
 
class ResilientSystem {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private worker: WorkerState = "Stopped"; // => Region 1: Worker
  // => Initialized alongside FSM state
  private monitor: MonitorState = "Monitoring"; // => Region 2: Monitor
  // => Initialized alongside FSM state
 
  getCurrentState(): { worker: WorkerState; monitor: MonitorState } {
    // => Query method: read current FSM state
    return { worker: this.worker, monitor: this.monitor }; // => Returns value to caller
    // => Return computed result
  }
 
  handleEvent(event: Event): void {
    // => Event handler: main FSM dispatch method
    if (event === "work" && this.worker === "Stopped") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Event type check
      this.worker = "Working"; // => Worker starts
    } else if (event === "error" && this.worker === "Working") {
      // => Logical AND: both conditions must be true
      this.worker = "Error"; // => Worker errors
      this.monitor = "AlertSent"; // => Error propagates to Monitor region
      console.log("Error propagated: Worker → Monitor"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    } else if (event === "acknowledge" && this.monitor === "AlertSent") {
      // => Logical AND: both conditions must be true
      this.monitor = "Monitoring"; // => Monitor acknowledges alert
      // => Worker region unchanged (error persists)
    } else if (event === "stop") {
      this.worker = "Stopped"; // => Worker stops
      if (this.monitor === "AlertSent") {
        // => Conditional branch
        // => Conditional check
        // => Branch execution based on condition
        this.monitor = "Monitoring"; // => Monitor auto-clears alert on stop
        console.log("Monitor cleared on worker stop"); // => Output for verification
        // => Debug/audit output
        // => Log for observability
      }
    }
  }
}
 
// Usage
const resilientSys = new ResilientSystem(); // => worker: Stopped, monitor: Monitoring
console.log(resilientSys.getCurrentState()); // => Output for verification
// => Chained method calls or nested operations
// => Query method: read current FSM state
// => Output: { worker: 'Stopped', monitor: 'Monitoring' }
 
resilientSys.handleEvent("work"); // => Worker: Stopped → Working
console.log(resilientSys.getCurrentState()); // => Output for verification
// => Chained method calls or nested operations
// => Query method: read current FSM state
// => Output: { worker: 'Working', monitor: 'Monitoring' }
 
resilientSys.handleEvent("error"); // => Error propagates across regions
// => Output: Error propagated: Worker → Monitor
console.log(resilientSys.getCurrentState()); // => Output for verification
// => Chained method calls or nested operations
// => Query method: read current FSM state
// => Output: { worker: 'Error', monitor: 'AlertSent' }
 
resilientSys.handleEvent("acknowledge"); // => Monitor clears, worker error persists
console.log(resilientSys.getCurrentState()); // => Output for verification
// => Chained method calls or nested operations
// => Query method: read current FSM state
// => Output: { worker: 'Error', monitor: 'Monitoring' }
 
resilientSys.handleEvent("stop"); // => Worker stops, monitor clears
// => Output: Monitor cleared on worker stop
console.log(resilientSys.getCurrentState()); // => Output for verification
// => Chained method calls or nested operations
// => Query method: read current FSM state
// => Output: { worker: 'Stopped', monitor: 'Monitoring' }

Key Takeaway: Error handling across parallel regions can be isolated (error stays in one region) or propagating (error triggers transitions in other regions). Design choice depends on failure semantics.

Why It Matters: Error propagation strategy impacts system resilience. Serverless FSMs isolate errors - if one function instance errors, it doesn't affect parallel instances. But circuit breakers use propagating errors - if Worker region exceeds error threshold, it forces CircuitBreaker region to "Open" state, stopping all traffic. Choose isolation for independent failures, propagation for cascading protection.

History States (Examples 45-48)

Example 45: Shallow History State

Shallow history remembers the most recent immediate substate of a parent state, enabling resume functionality.

stateDiagram-v2
    [*] --> Off
 
    state On {
        [*] --> Low
        Low --> Medium: increase
        Medium --> High: increase
        High --> Medium: decrease
        Medium --> Low: decrease
 
        state historyState <<history>>
    }
 
    Off --> On: powerOn
    On --> Off: powerOff
    Off --> historyState: resume
 
    classDef offState fill:#0173B2,stroke:#000,color:#fff
    classDef onState fill:#DE8F05,stroke:#000,color:#fff
    classDef levelState fill:#029E73,stroke:#000,color:#fff
 
    class Off offState
    class On onState
    class Low,Medium,High levelState

TypeScript Implementation:

// Shallow history: Remembers last immediate substate
type State = "Off" | "On.Low" | "On.Medium" | "On.High"; // => States with hierarchy
// => Type system ensures only valid states used
type Event = "powerOn" | "powerOff" | "resume" | "increase" | "decrease"; // => Events
// => Events trigger state transitions
 
class BrightnessControl {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: State = "Off"; // => Current state
  // => FSM begins execution in Off state
  private history: "On.Low" | "On.Medium" | "On.High" | null = null; // => Last On substate
  // => Initialized alongside FSM state
 
  getCurrentState(): State {
    // => Query method: read current FSM state
    return this.state; // => Returns value to caller
    // => Return current state value
  }
 
  private saveHistory(): void {
    // => Extended state (data beyond FSM state)
    if (this.state.startsWith("On.")) {
      // => State-based guard condition
      // => Chained method calls or nested operations
      // => Conditional check
      // => Branch execution based on condition
      this.history = this.state as any; // => Save current On substate
      console.log(`History saved: ${this.history}`); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    }
  }
 
  handleEvent(event: Event): void {
    // => Event handler: main FSM dispatch method
    if (event === "powerOn" && this.state === "Off") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Event type check
      // => Combined (state, event) guard
      this.state = "On.Low"; // => Default: enter at Low
      this.saveHistory();
    } else if (event === "powerOff" && this.state.startsWith("On.")) {
      // => Logical AND: both conditions must be true
      this.saveHistory(); // => Save before exiting
      this.state = "Off"; // => Exit to Off
    } else if (event === "resume" && this.state === "Off" && this.history) {
      // => Logical AND: both conditions must be true
      this.state = this.history; // => Restore last On substate from history
      console.log(`Resumed from history: ${this.state}`); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    } else if (event === "increase") {
      if (this.state === "On.Low")
        this.state = "On.Medium"; // => State-based guard condition
      // => Conditional check
      // => Branch execution based on condition
      else if (this.state === "On.Medium") this.state = "On.High"; // => Alternative conditional branch
      this.saveHistory();
    } else if (event === "decrease") {
      if (this.state === "On.High")
        this.state = "On.Medium"; // => State-based guard condition
      // => Conditional check
      // => Branch execution based on condition
      else if (this.state === "On.Medium") this.state = "On.Low"; // => Alternative conditional branch
      this.saveHistory();
    }
  }
}
 
// Usage
const brightness = new BrightnessControl(); // => state: Off, history: null
brightness.handleEvent("powerOn"); // => Off → On.Low
// => Output: History saved: On.Low
console.log(brightness.getCurrentState()); // => Output: On.Low
 
brightness.handleEvent("increase"); // => On.Low → On.Medium
// => Output: History saved: On.Medium
console.log(brightness.getCurrentState()); // => Output: On.Medium
 
brightness.handleEvent("increase"); // => On.Medium → On.High
// => Output: History saved: On.High
console.log(brightness.getCurrentState()); // => Output: On.High
 
brightness.handleEvent("powerOff"); // => On.High → Off (save High in history)
// => Output: History saved: On.High
console.log(brightness.getCurrentState()); // => Output: Off
 
brightness.handleEvent("resume"); // => Off → On.High (restore from history)
// => Output: Resumed from history: On.High
console.log(brightness.getCurrentState()); // => Output: On.High

Key Takeaway: Shallow history saves the last immediate substate before exiting a parent state. Resume restores that exact substate instead of entering at default.

Why It Matters: History states enable "resume where you left off" UX. Music apps use history: pause at 2:37 in Song 5, close app, reopen → resumes at 2:37 in Song 5 instead of starting playlist from beginning. Without history, users lose context on every app restart, creating frustration. Deep history states capture the full substate hierarchy, enabling restoration of complex nested state configurations that would be impractical to reconstruct manually.

Example 46: Deep History State

Deep history remembers the entire state hierarchy across all nesting levels, enabling full context restoration.

TypeScript Implementation:

// Deep history: Remembers full state hierarchy
type State = "Off" | "On.Playing.Song1" | "On.Playing.Song2" | "On.Paused"; // => Multi-level hierarchy
// => Type system ensures only valid states used
type Event = "powerOn" | "powerOff" | "resume" | "play" | "pause" | "nextSong"; // => Events
// => Events trigger state transitions
 
class MusicPlayer {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: State = "Off"; // => Current state
  // => FSM begins execution in Off state
  private deepHistory: Exclude<State, "Off"> | null = null; // => Full state path saved
  // => Initialized alongside FSM state
 
  getCurrentState(): State {
    // => Query method: read current FSM state
    return this.state; // => Returns value to caller
    // => Return current state value
  }
 
  private saveDeepHistory(): void {
    // => Extended state (data beyond FSM state)
    if (this.state !== "Off") {
      // => State-based guard condition
      // => Conditional check
      // => Branch execution based on condition
      this.deepHistory = this.state as any; // => Save complete state path
      console.log(`Deep history saved: ${this.deepHistory}`); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    }
  }
 
  handleEvent(event: Event): void {
    // => Event handler: main FSM dispatch method
    if (event === "powerOn" && this.state === "Off") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Event type check
      // => Combined (state, event) guard
      this.state = "On.Playing.Song1"; // => Default: enter at Song1
      this.saveDeepHistory();
    } else if (event === "powerOff" && this.state !== "Off") {
      // => Logical AND: both conditions must be true
      this.saveDeepHistory(); // => Save before exiting
      this.state = "Off"; // => State transition execution
      // => Transition: set state to Off
      // => State mutation (core FSM operation)
    } else if (event === "resume" && this.state === "Off" && this.deepHistory) {
      // => Logical AND: both conditions must be true
      this.state = this.deepHistory; // => Restore complete state path
      console.log(`Deep resume: ${this.state}`); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    } else if (event === "pause" && this.state.startsWith("On.Playing")) {
      // => Logical AND: both conditions must be true
      this.saveDeepHistory();
      this.state = "On.Paused"; // => Playing → Paused
    } else if (event === "play" && this.state === "On.Paused" && this.deepHistory?.startsWith("On.Playing")) {
      // => Logical AND: both conditions must be true
      this.state = this.deepHistory; // => Resume exact playing state
      console.log(`Resume play: ${this.state}`); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    } else if (event === "nextSong" && this.state === "On.Playing.Song1") {
      // => Logical AND: both conditions must be true
      this.state = "On.Playing.Song2"; // => State transition execution
      this.saveDeepHistory();
    }
  }
}
 
// Usage
const player = new MusicPlayer(); // => state: Off
player.handleEvent("powerOn"); // => Off → On.Playing.Song1
// => Output: Deep history saved: On.Playing.Song1
console.log(player.getCurrentState()); // => Output: On.Playing.Song1
 
player.handleEvent("nextSong"); // => Song1 → Song2
// => Output: Deep history saved: On.Playing.Song2
console.log(player.getCurrentState()); // => Output: On.Playing.Song2
 
player.handleEvent("pause"); // => Playing.Song2 → Paused
// => Output: Deep history saved: On.Playing.Song2
console.log(player.getCurrentState()); // => Output: On.Paused
 
player.handleEvent("play"); // => Paused → restore exact playing state (Song2)
// => Output: Resume play: On.Playing.Song2
console.log(player.getCurrentState()); // => Output: On.Playing.Song2
 
player.handleEvent("powerOff"); // => Save and power off
// => Output: Deep history saved: On.Playing.Song2
player.handleEvent("resume"); // => Resume full state path
// => Output: Deep resume: On.Playing.Song2
console.log(player.getCurrentState()); // => Output: On.Playing.Song2

Key Takeaway: Deep history preserves the complete state hierarchy (parent + all substate levels). Restoring deep history returns to exact nested state before exit.

Why It Matters: Deep history preserves complex context. Video editors use deep history: editing timeline at 3:45, layer 7, zoom 200%, tool: trim. If app crashes and restores from deep history, user returns to exact editing context. Shallow history would restore only top-level "Editing" state, losing timeline position, layer, zoom, and tool selection.

Example 47: History State Timeout

History states can expire after a timeout, reverting to default entry instead of historical state.

TypeScript Implementation:

// History with timeout: Expire after 5 seconds
type State = "Idle" | "Working" | "Paused"; // => States
// => Type system ensures only valid states used
type Event = "start" | "pause" | "resume"; // => Events
// => Events trigger state transitions
 
class TimedHistory {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: State = "Idle"; // => Current state
  // => FSM begins execution in Idle state
  private history: State | null = null; // => Saved state
  // => Initialized alongside FSM state
  private historyTimestamp: number | null = null; // => When history was saved
  // => Initialized alongside FSM state
  private readonly HISTORY_TIMEOUT = 5000; // => 5 seconds in milliseconds
  // => Initialized alongside FSM state
 
  getCurrentState(): State {
    // => Query method: read current FSM state
    return this.state; // => Returns value to caller
    // => Return current state value
  }
 
  private saveHistory(): void {
    // => Extended state (data beyond FSM state)
    this.history = this.state; // => Save state
    this.historyTimestamp = Date.now(); // => Save timestamp
    console.log(`History saved: ${this.history} at ${this.historyTimestamp}`); // => Output for verification
    // => Debug/audit output
    // => Log for observability
  }
 
  private isHistoryValid(): boolean {
    // => Extended state (data beyond FSM state)
    if (!this.history || !this.historyTimestamp) return false; // => Conditional branch
    // => Logical OR: either condition can be true
    // => Return computed result
    const elapsed = Date.now() - this.historyTimestamp; // => Time since save
    return elapsed < this.HISTORY_TIMEOUT; // => Valid if within timeout
  }
 
  handleEvent(event: Event): void {
    // => Event handler: main FSM dispatch method
    if (event === "start" && this.state === "Idle") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Event type check
      // => Combined (state, event) guard
      this.state = "Working"; // => State transition execution
      // => Transition: set state to Working
      // => State mutation (core FSM operation)
    } else if (event === "pause" && this.state === "Working") {
      // => Logical AND: both conditions must be true
      this.saveHistory(); // => Save Working state
      this.state = "Paused"; // => State transition execution
      // => Transition: set state to Paused
      // => State mutation (core FSM operation)
    } else if (event === "resume" && this.state === "Paused") {
      // => Logical AND: both conditions must be true
      if (this.isHistoryValid()) {
        // => Conditional branch
        // => Chained method calls or nested operations
        // => Conditional check
        // => Branch execution based on condition
        this.state = this.history!; // => Restore from history
        console.log(`Resumed from history: ${this.state}`); // => Output for verification
        // => Debug/audit output
        // => Log for observability
      } else {
        // => Fallback branch
        this.state = "Idle"; // => History expired → default state
        console.log("History expired: reset to Idle"); // => Output for verification
        // => Debug/audit output
        // => Log for observability
      }
      this.history = null; // => Clear history after use
      this.historyTimestamp = null;
    }
  }
}
 
// Usage
const timedFSM = new TimedHistory(); // => state: Idle
timedFSM.handleEvent("start"); // => Idle → Working
console.log(timedFSM.getCurrentState()); // => Output: Working
 
timedFSM.handleEvent("pause"); // => Working → Paused (save history)
// => Output: History saved: Working at [timestamp]
console.log(timedFSM.getCurrentState()); // => Output: Paused
 
// Resume immediately (within timeout)
timedFSM.handleEvent("resume"); // => Paused → Working (history valid)
// => Output: Resumed from history: Working
console.log(timedFSM.getCurrentState()); // => Output: Working
 
// Pause again and wait for timeout
timedFSM.handleEvent("pause");
// => Event handler: main FSM dispatch method
console.log("Waiting 6 seconds for history to expire..."); // => Output for verification
// => Debug/audit output
// => Log for observability
// (In real code: setTimeout or await delay)
// After 6 seconds:
// timedFSM.handleEvent("resume"); // => Paused → Idle (history expired)
// => Output: History expired: reset to Idle

Key Takeaway: History states can have expiration policies. After timeout, FSM enters default state instead of historical state, preventing stale context restoration.

Why It Matters: Stale history creates security and UX issues. Banking apps expire session history after 15 minutes - if you paused at "Transfer $1000" and resume 2 hours later, you want a fresh session (re-authenticate), not restoration to transfer screen with stale auth token. History timeout balances "resume where you left off" UX with security requirements.

Example 48: Conditional History Restoration

History restoration can be conditional based on validation rules, enabling safe context restoration.

TypeScript Implementation:

// Conditional history: Restore only if validation passes
type State = "Disconnected" | "Connected.Syncing" | "Connected.Idle"; // => States
// => Type system ensures only valid states used
type Event = "connect" | "disconnect" | "sync" | "reconnect"; // => Events
// => Events trigger state transitions
 
interface HistoryContext {
  // => Type declaration defines structure
  state: State;
  timestamp: number;
  networkQuality: "good" | "poor"; // => Additional context
}
 
class ConditionalHistory {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: State = "Disconnected"; // => State field: stores current FSM state privately
  // => Mutable state storage (single source of truth)
  // => FSM begins execution in Disconnected state
  private history: HistoryContext | null = null; // => Field declaration: class member variable
  // => Extended state (data beyond FSM state)
  // => Initialized alongside FSM state
 
  getCurrentState(): State {
    // => Query method: read current FSM state
    return this.state; // => Returns value to caller
    // => Return current state value
  }
 
  private saveHistory(networkQuality: "good" | "poor"): void {
    // => Extended state (data beyond FSM state)
    this.history = {
      state: this.state,
      timestamp: Date.now(),
      networkQuality, // => Save additional context
    };
    console.log(`History saved: ${this.state} (network: ${networkQuality})`); // => Output for verification
    // => Chained method calls or nested operations
    // => Debug/audit output
    // => Log for observability
  }
 
  private canRestoreHistory(currentNetworkQuality: "good" | "poor"): boolean {
    // => Extended state (data beyond FSM state)
    if (!this.history) return false; // => Conditional branch
    // => Return computed result
 
    // Validation 1: Check timeout (5 seconds)
    const elapsed = Date.now() - this.history.timestamp; // => Variable declaration and assignment
    // => Initialize elapsed
    if (elapsed > 5000) {
      // => Conditional branch
      // => Conditional check
      // => Branch execution based on condition
      console.log("History validation failed: timeout"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      return false; // => Returns value to caller
      // => Return computed result
    }
 
    // Validation 2: Check network quality consistency
    if (this.history.networkQuality === "good" && currentNetworkQuality === "poor") {
      // => Conditional branch
      // => Logical AND: both conditions must be true
      // => Conditional check
      // => Branch execution based on condition
      console.log("History validation failed: network degraded"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      return false; // => Don't restore "Syncing" if network is now poor
    }
 
    console.log("History validation passed"); // => Output for verification
    // => Debug/audit output
    // => Log for observability
    return true; // => Returns value to caller
    // => Return computed result
  }
 
  handleEvent(event: Event, networkQuality: "good" | "poor" = "good"): void {
    // => Event handler: main FSM dispatch method
    if (event === "connect" && this.state === "Disconnected") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Event type check
      // => Combined (state, event) guard
      this.state = "Connected.Idle"; // => State transition execution
    } else if (event === "sync" && this.state === "Connected.Idle") {
      // => Logical AND: both conditions must be true
      this.state = "Connected.Syncing"; // => State transition execution
      this.saveHistory(networkQuality);
    } else if (event === "disconnect" && this.state.startsWith("Connected.")) {
      // => Logical AND: both conditions must be true
      this.saveHistory(networkQuality); // => Save before disconnect
      this.state = "Disconnected"; // => State transition execution
      // => Transition: set state to Disconnected
      // => State mutation (core FSM operation)
    } else if (event === "reconnect" && this.state === "Disconnected") {
      // => Logical AND: both conditions must be true
      if (this.canRestoreHistory(networkQuality)) {
        // => Conditional branch
        // => Chained method calls or nested operations
        // => Conditional check
        // => Branch execution based on condition
        this.state = this.history!.state as State; // => Restore validated history
        console.log(`Restored: ${this.state}`); // => Output for verification
        // => Debug/audit output
        // => Log for observability
      } else {
        // => Fallback branch
        this.state = "Connected.Idle"; // => Validation failed → default state
        console.log("Fallback to default: Connected.Idle"); // => Output for verification
        // => Debug/audit output
        // => Log for observability
      }
    }
  }
}
 
// Usage - Successful restoration
const condFSM = new ConditionalHistory(); // => Instance creation via constructor
// => Create new instance
// => Initialize condFSM
condFSM.handleEvent("connect"); // => Disconnected → Connected.Idle
condFSM.handleEvent("sync", "good"); // => Idle → Syncing (good network)
// => Output: History saved: Connected.Syncing (network: good)
 
condFSM.handleEvent("disconnect", "good"); // => Syncing → Disconnected
// => Output: History saved: Connected.Syncing (network: good)
 
condFSM.handleEvent("reconnect", "good"); // => Restore (validation passes)
// => Output: History validation passed
// =>         Restored: Connected.Syncing
console.log(condFSM.getCurrentState()); // => Output: Connected.Syncing
 
// Usage - Failed restoration (network degraded)
const condFSM2 = new ConditionalHistory(); // => Instance creation via constructor
// => Create new instance
// => Initialize condFSM2
condFSM2.handleEvent("connect");
// => Event handler: main FSM dispatch method
condFSM2.handleEvent("sync", "good"); // => Save with good network
condFSM2.handleEvent("disconnect", "good");
// => Event handler: main FSM dispatch method
condFSM2.handleEvent("reconnect", "poor"); // => Reconnect with poor network
// => Output: History validation failed: network degraded
// =>         Fallback to default: Connected.Idle
console.log(condFSM2.getCurrentState()); // => Output: Connected.Idle

Key Takeaway: Conditional history restoration validates saved context before restoration. If validation fails, FSM falls back to default state instead of restoring potentially invalid state.

Why It Matters: Unconditional restoration can restore invalid states. A video streaming app paused at 4K quality on WiFi shouldn't restore 4K on cellular (bandwidth insufficient). Conditional history checks network bandwidth before restoring playback quality. Stripe's payment FSM validates saved payment amount against current exchange rate before restoring "Confirm Payment" state - prevents showing stale amounts.

State Pattern Implementation (Examples 49-53)

Example 49: State Pattern - Encapsulating State Behavior

The State Pattern encapsulates state-specific behavior in separate classes, enabling polymorphic state transitions.

TypeScript Implementation:

// State Pattern: Each state is a class implementing State interface
interface State {
  // => Type declaration defines structure
  handle(context: TrafficLight): void; // => Each state handles its own transitions
  toString(): string; // => State name for logging
}
 
class RedState implements State {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  handle(context: TrafficLight): void {
    // => Event handler: main FSM dispatch method
    console.log("Red light: Stop. Transitioning to Green..."); // => Output for verification
    // => Debug/audit output
    // => Log for observability
    context.setState(new GreenState()); // => Red → Green
    // => Traffic light state management
  }
  toString(): string {
    return "Red"; // => State name
  }
}
 
class GreenState implements State {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  handle(context: TrafficLight): void {
    // => Event handler: main FSM dispatch method
    console.log("Green light: Go. Transitioning to Yellow..."); // => Output for verification
    // => Debug/audit output
    // => Log for observability
    context.setState(new YellowState()); // => Green → Yellow
    // => Traffic light state management
  }
  toString(): string {
    return "Green"; // => Returns value to caller
    // => Return computed result
  }
}
 
class YellowState implements State {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  handle(context: TrafficLight): void {
    // => Event handler: main FSM dispatch method
    console.log("Yellow light: Caution. Transitioning to Red..."); // => Output for verification
    // => Debug/audit output
    // => Log for observability
    context.setState(new RedState()); // => Yellow → Red
    // => Traffic light state management
  }
  toString(): string {
    return "Yellow"; // => Returns value to caller
    // => Return computed result
  }
}
 
class TrafficLight {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: State; // => Current state object
 
  constructor() {
    this.state = new RedState(); // => Initial state: Red
    // => Traffic light state management
  }
 
  setState(state: State): void {
    this.state = state; // => Transition to new state
    console.log(`State changed to: ${state.toString()}`); // => Output for verification
    // => Chained method calls or nested operations
    // => Debug/audit output
    // => Log for observability
  }
 
  next(): void {
    this.state.handle(this); // => Delegate to current state
  }
 
  getCurrentState(): string {
    // => Query method: read current FSM state
    return this.state.toString(); // => State name
  }
}
 
// Usage
const traffic = new TrafficLight(); // => state: Red
console.log(`Current: ${traffic.getCurrentState()}`); // => Output: Current: Red
 
traffic.next(); // => Red handles: Red → Green
// => Output: Red light: Stop. Transitioning to Green...
// =>         State changed to: Green
console.log(`Current: ${traffic.getCurrentState()}`); // => Output: Current: Green
 
traffic.next(); // => Green handles: Green → Yellow
// => Output: Green light: Go. Transitioning to Yellow...
// =>         State changed to: Yellow
console.log(`Current: ${traffic.getCurrentState()}`); // => Output: Current: Yellow
 
traffic.next(); // => Yellow handles: Yellow → Red
// => Output: Yellow light: Caution. Transitioning to Red...
// =>         State changed to: Red

Key Takeaway: State Pattern encapsulates state-specific behavior in separate classes. Each state knows its own transitions, eliminating large conditional blocks in a single class.

Why It Matters: State Pattern makes complex FSMs maintainable. Without it, a multi-state FSM becomes a large switch statement with nested conditions - adding a new state requires modifying the monolithic switch. With State Pattern, adding a new state is just creating a new class implementing the State interface. Booking FSMs use State Pattern for multiple booking states (searching, selecting, confirming, paying, etc.) - each state is a small class instead of a large switch statement.

Example 50: State Pattern with Entry/Exit Actions

State classes can implement entry and exit actions, encapsulating state initialization and cleanup.

TypeScript Implementation:

// State Pattern with lifecycle hooks
interface StateWithLifecycle {
  // => Type declaration defines structure
  onEnter(context: Application): void; // => Called when entering state
  onExit(context: Application): void; // => Called when exiting state
  handle(event: string, context: Application): void; // => Handle events
  getName(): string;
}
 
class LoadingState implements StateWithLifecycle {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  onEnter(context: Application): void {
    console.log("Loading: Initialize resources"); // => Entry action
    // => Log for observability
  }
 
  onExit(context: Application): void {
    console.log("Loading: Cleanup loaders"); // => Exit action
    // => Log for observability
  }
 
  handle(event: string, context: Application): void {
    // => Event handler: main FSM dispatch method
    if (event === "loaded") {
      // => Event type guard condition
      // => Event type check
      context.transitionTo(new ReadyState()); // => Loading → Ready
    }
  }
 
  getName(): string {
    return "Loading"; // => Returns value to caller
    // => Return computed result
  }
}
 
class ReadyState implements StateWithLifecycle {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  onEnter(context: Application): void {
    console.log("Ready: Application ready for use"); // => Output for verification
    // => Debug/audit output
    // => Log for observability
  }
 
  onExit(context: Application): void {
    console.log("Ready: Pausing operations"); // => Output for verification
    // => Debug/audit output
    // => Log for observability
  }
 
  handle(event: string, context: Application): void {
    // => Event handler: main FSM dispatch method
    if (event === "error") {
      // => Event type guard condition
      // => Event type check
      context.transitionTo(new ErrorState()); // => Ready → Error
    }
  }
 
  getName(): string {
    return "Ready"; // => Returns value to caller
    // => Return computed result
  }
}
 
class ErrorState implements StateWithLifecycle {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  onEnter(context: Application): void {
    console.log("Error: Logging error, notifying user"); // => Output for verification
    // => Debug/audit output
    // => Log for observability
  }
 
  onExit(context: Application): void {
    console.log("Error: Clearing error state"); // => Output for verification
    // => Debug/audit output
    // => Log for observability
  }
 
  handle(event: string, context: Application): void {
    // => Event handler: main FSM dispatch method
    if (event === "retry") {
      // => Event type guard condition
      // => Event type check
      context.transitionTo(new LoadingState()); // => Error → Loading (retry)
    }
  }
 
  getName(): string {
    return "Error"; // => Returns value to caller
    // => Return computed result
  }
}
 
class Application {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: StateWithLifecycle; // => State field: stores current FSM state privately
  // => State variable declaration
 
  constructor() {
    this.state = new LoadingState(); // => State transition execution
    // => Constructor creates new object instance
    // => Create new instance
    this.state.onEnter(this); // => Execute entry action for initial state
  }
 
  transitionTo(newState: StateWithLifecycle): void {
    console.log(`Transitioning: ${this.state.getName()} → ${newState.getName()}`); // => Output for verification
    // => Chained method calls or nested operations
    // => Debug/audit output
    // => Log for observability
    this.state.onExit(this); // => Exit current state
    this.state = newState; // => Switch state
    this.state.onEnter(this); // => Enter new state
  }
 
  handleEvent(event: string): void {
    // => Event handler: main FSM dispatch method
    this.state.handle(event, this); // => Delegate to current state
  }
 
  getCurrentState(): string {
    // => Query method: read current FSM state
    return this.state.getName(); // => Returns value to caller
    // => Return current state value
  }
}
 
// Usage
const app = new Application(); // => state: Loading (onEnter executes)
// => Output: Loading: Initialize resources
console.log(`Current: ${app.getCurrentState()}`); // => Output: Current: Loading
 
app.handleEvent("loaded"); // => Loading → Ready
// => Output: Transitioning: Loading → Ready
// =>         Loading: Cleanup loaders
// =>         Ready: Application ready for use
console.log(`Current: ${app.getCurrentState()}`); // => Output: Current: Ready
 
app.handleEvent("error"); // => Ready → Error
// => Output: Transitioning: Ready → Error
// =>         Ready: Pausing operations
// =>         Error: Logging error, notifying user
console.log(`Current: ${app.getCurrentState()}`); // => Output: Current: Error
 
app.handleEvent("retry"); // => Error → Loading
// => Output: Transitioning: Error → Loading
// =>         Error: Clearing error state
// =>         Loading: Initialize resources

Key Takeaway: State classes can implement onEnter/onExit lifecycle hooks. The context orchestrates transitions, ensuring entry/exit actions execute in correct order (exit old → switch → enter new).

Why It Matters: Entry/exit actions in state classes prevent duplication and ensure consistency. Without lifecycle hooks, every transition manually calls cleanup/initialization code, risking forgotten cleanup (resource leaks) or missed initialization (broken state). React component lifecycle methods use this pattern - componentDidMount/componentWillUnmount guarantee setup/teardown regardless of how component enters/exits. State class lifecycle hooks also enable aspect-oriented concerns like performance monitoring and logging to be attached to state boundaries without modifying business logic.

Example 51: State Pattern with Guards

State transitions can include guard conditions that determine if a transition is allowed.

TypeScript Implementation:

// State Pattern with guard conditions
interface GuardedState {
  // => Type declaration defines structure
  canTransition(event: string, context: VendingMachine): boolean; // => Guard condition
  // => Returns boolean without changing state
  handle(event: string, context: VendingMachine): void;
  // => Event handler: main FSM dispatch method
  getName(): string;
}
 
class IdleState implements GuardedState {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  canTransition(event: string, context: VendingMachine): boolean {
    // => Guard: validates transition possibility
    // => Returns boolean without changing state
    if (event === "insertCoin") {
      // => Event type guard condition
      // => Event type check
      return context.getBalance() < 100; // => Guard: allow only if balance < $1.00
    }
    return false; // => Returns value to caller
    // => Return computed result
  }
 
  handle(event: string, context: VendingMachine): void {
    // => Event handler: main FSM dispatch method
    if (event === "insertCoin" && this.canTransition(event, context)) {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Guard: validates transition possibility
      // => Returns boolean without changing state
      context.addBalance(25); // => Add $0.25
      console.log(`Coin inserted. Balance: $${context.getBalance() / 100}`); // => Output for verification
      // => Chained method calls or nested operations
      // => Debug/audit output
      // => Log for observability
      if (context.getBalance() >= 50) {
        // => Conditional branch
        // => Chained method calls or nested operations
        // => Conditional check
        // => Branch execution based on condition
        // => Guard for state transition
        context.transitionTo(new ReadyState()); // => Idle → Ready (enough money)
      }
    } else if (!this.canTransition(event, context)) {
      // => Chained method calls or nested operations
      // => Guard: validates transition possibility
      // => Returns boolean without changing state
      console.log("Guard failed: Balance limit reached"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    }
  }
 
  getName(): string {
    return "Idle"; // => Returns value to caller
    // => Return computed result
  }
}
 
class ReadyState implements GuardedState {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  canTransition(event: string, context: VendingMachine): boolean {
    // => Guard: validates transition possibility
    // => Returns boolean without changing state
    if (event === "selectItem") {
      // => Event type guard condition
      // => Event type check
      return context.getBalance() >= 50; // => Guard: need at least $0.50
    }
    return event === "cancel"; // => Cancel always allowed
  }
 
  handle(event: string, context: VendingMachine): void {
    // => Event handler: main FSM dispatch method
    if (event === "selectItem" && this.canTransition(event, context)) {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Guard: validates transition possibility
      // => Returns boolean without changing state
      context.transitionTo(new DispensingState()); // => Ready → Dispensing
    } else if (event === "cancel") {
      console.log(`Refunding $${context.getBalance() / 100}`); // => Output for verification
      // => Chained method calls or nested operations
      // => Debug/audit output
      // => Log for observability
      context.resetBalance();
      context.transitionTo(new IdleState()); // => Ready → Idle (cancel)
      // => Workflow state progression
    } else {
      // => Fallback branch
      console.log("Guard failed: Insufficient balance"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    }
  }
 
  getName(): string {
    return "Ready"; // => Returns value to caller
    // => Return computed result
  }
}
 
class DispensingState implements GuardedState {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  canTransition(event: string, context: VendingMachine): boolean {
    // => Guard: validates transition possibility
    // => Returns boolean without changing state
    return event === "dispensed"; // => Only transition on dispensed
  }
 
  handle(event: string, context: VendingMachine): void {
    // => Event handler: main FSM dispatch method
    console.log("Dispensing item..."); // => Output for verification
    // => Debug/audit output
    // => Log for observability
    context.deductItemCost(50); // => Deduct $0.50
    if (this.canTransition("dispensed", context)) {
      // => Conditional branch
      // => Chained method calls or nested operations
      // => Guard: validates transition possibility
      // => Returns boolean without changing state
      context.transitionTo(new IdleState()); // => Dispensing → Idle
      // => Workflow state progression
    }
  }
 
  getName(): string {
    return "Dispensing"; // => Returns value to caller
    // => Return computed result
  }
}
 
class VendingMachine {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: GuardedState; // => State field: stores current FSM state privately
  // => State variable declaration
  private balance = 0; // => Balance in cents
  // => Initialized alongside FSM state
 
  constructor() {
    this.state = new IdleState(); // => State transition execution
    // => Constructor creates new object instance
    // => Create new instance
  }
 
  getBalance(): number {
    return this.balance; // => Returns value to caller
    // => Return computed result
  }
 
  addBalance(amount: number): void {
    this.balance += amount;
    // => Modify state data
    // => Update extended state data
  }
 
  deductItemCost(cost: number): void {
    this.balance -= cost;
    // => Modify state data
    // => Update extended state data
  }
 
  resetBalance(): void {
    this.balance = 0;
  }
 
  transitionTo(newState: GuardedState): void {
    console.log(`Transition: ${this.state.getName()} → ${newState.getName()}`); // => Output for verification
    // => Chained method calls or nested operations
    // => Debug/audit output
    // => Log for observability
    this.state = newState; // => State transition execution
  }
 
  handleEvent(event: string): void {
    // => Event handler: main FSM dispatch method
    this.state.handle(event, this); // => Delegate to state
  }
 
  getCurrentState(): string {
    // => Query method: read current FSM state
    return this.state.getName(); // => Returns value to caller
    // => Return current state value
  }
}
 
// Usage
const vending = new VendingMachine(); // => state: Idle, balance: $0
console.log(`Current: ${vending.getCurrentState()}`); // => Output: Current: Idle
 
vending.handleEvent("insertCoin"); // => Add $0.25 (balance: $0.25)
// => Output: Coin inserted. Balance: $0.25
console.log(`Balance: $${vending.getBalance() / 100}`); // => Output: Balance: $0.25
 
vending.handleEvent("insertCoin"); // => Add $0.25 (balance: $0.50) → Ready
// => Output: Coin inserted. Balance: $0.5
// =>         Transition: Idle → Ready
console.log(`Current: ${vending.getCurrentState()}`); // => Output: Current: Ready
 
vending.handleEvent("selectItem"); // => Ready → Dispensing (guard passes)
// => Output: Transition: Ready → Dispensing
// =>         Dispensing item...
// =>         Transition: Dispensing → Idle
console.log(`Current: ${vending.getCurrentState()}`); // => Output: Current: Idle

Key Takeaway: Guard conditions enable conditional transitions. States implement canTransition() to validate preconditions before executing transition logic.

Why It Matters: Guards prevent invalid transitions at the state level, not in calling code. Without guards, every caller must check conditions before triggering transitions, duplicating validation logic. Payment FSMs use guards: "charge" event only transitions from Authorized to Charged if card isn't expired (guard). If expired, guard fails and FSM stays in Authorized state, logging rejection.

Example 52: State Pattern with Context Data

State objects can access and modify context data, enabling stateful behavior based on accumulated data.

TypeScript Implementation:

// State Pattern with shared context data
interface ConnectionState {
  // => Type declaration defines structure
  handle(event: string, context: ConnectionContext): void;
  // => Event handler: main FSM dispatch method
  getName(): string;
}
 
class DisconnectedState implements ConnectionState {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  handle(event: string, context: ConnectionContext): void {
    // => Event handler: main FSM dispatch method
    if (event === "connect") {
      // => Event type guard condition
      // => Event type check
      context.incrementAttempts(); // => Track connection attempts
      console.log(`Connection attempt ${context.getAttempts()}`); // => Output for verification
      // => Chained method calls or nested operations
      // => Debug/audit output
      // => Log for observability
 
      if (context.getAttempts() > 3) {
        // => Conditional branch
        // => Chained method calls or nested operations
        // => Conditional check
        // => Branch execution based on condition
        // => Check context data
        console.log("Too many attempts. Backing off..."); // => Output for verification
        // => Debug/audit output
        // => Log for observability
        context.transitionTo(new BackoffState()); // => Disconnected → Backoff
      } else {
        // => Fallback branch
        context.transitionTo(new ConnectingState()); // => Disconnected → Connecting
      }
    }
  }
 
  getName(): string {
    return "Disconnected"; // => Returns value to caller
    // => Return computed result
  }
}
 
class ConnectingState implements ConnectionState {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  handle(event: string, context: ConnectionContext): void {
    // => Event handler: main FSM dispatch method
    if (event === "success") {
      // => Event type guard condition
      // => Event type check
      context.resetAttempts(); // => Reset attempt counter
      context.transitionTo(new ConnectedState()); // => Connecting → Connected
    } else if (event === "failure") {
      context.transitionTo(new DisconnectedState()); // => Retry
    }
  }
 
  getName(): string {
    return "Connecting"; // => Returns value to caller
    // => Return computed result
  }
}
 
class ConnectedState implements ConnectionState {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  handle(event: string, context: ConnectionContext): void {
    // => Event handler: main FSM dispatch method
    if (event === "disconnect") {
      // => Event type guard condition
      // => Event type check
      context.transitionTo(new DisconnectedState()); // => Connected → Disconnected
    }
  }
 
  getName(): string {
    return "Connected"; // => Returns value to caller
    // => Return computed result
  }
}
 
class BackoffState implements ConnectionState {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  handle(event: string, context: ConnectionContext): void {
    // => Event handler: main FSM dispatch method
    if (event === "retry") {
      // => Event type guard condition
      // => Event type check
      context.resetAttempts(); // => Reset counter after backoff
      console.log("Backoff complete. Retrying..."); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      context.transitionTo(new DisconnectedState()); // => Backoff → Disconnected
    }
  }
 
  getName(): string {
    return "Backoff"; // => Returns value to caller
    // => Return computed result
  }
}
 
class ConnectionContext {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: ConnectionState; // => State field: stores current FSM state privately
  // => State variable declaration
  private attempts = 0; // => Shared context data
  // => Initialized alongside FSM state
 
  constructor() {
    this.state = new DisconnectedState(); // => State transition execution
    // => Constructor creates new object instance
    // => Create new instance
  }
 
  getAttempts(): number {
    return this.attempts; // => Read context data
  }
 
  incrementAttempts(): void {
    this.attempts++; // => Modify context data
  }
 
  resetAttempts(): void {
    this.attempts = 0;
  }
 
  transitionTo(newState: ConnectionState): void {
    console.log(`Transition: ${this.state.getName()} → ${newState.getName()}`); // => Output for verification
    // => Chained method calls or nested operations
    // => Debug/audit output
    // => Log for observability
    this.state = newState; // => State transition execution
  }
 
  handleEvent(event: string): void {
    // => Event handler: main FSM dispatch method
    this.state.handle(event, this);
    // => Event handler: main FSM dispatch method
  }
 
  getCurrentState(): string {
    // => Query method: read current FSM state
    return this.state.getName(); // => Returns value to caller
    // => Return current state value
  }
}
 
// Usage
const conn = new ConnectionContext(); // => state: Disconnected, attempts: 0
 
conn.handleEvent("connect"); // => Attempt 1: Disconnected → Connecting
// => Output: Connection attempt 1
// =>         Transition: Disconnected → Connecting
 
conn.handleEvent("failure"); // => Connecting → Disconnected (retry)
// => Output: Transition: Connecting → Disconnected
 
conn.handleEvent("connect"); // => Attempt 2
// => Output: Connection attempt 2
// =>         Transition: Disconnected → Connecting
 
conn.handleEvent("failure"); // => Retry again
 
conn.handleEvent("connect"); // => Attempt 3
conn.handleEvent("failure");
// => Event handler: main FSM dispatch method
 
conn.handleEvent("connect"); // => Attempt 4: Too many → Backoff
// => Output: Connection attempt 4
// =>         Too many attempts. Backing off...
// =>         Transition: Disconnected → Backoff
 
conn.handleEvent("retry"); // => Backoff → Disconnected (reset)
// => Output: Backoff complete. Retrying...
// =>         Transition: Backoff → Disconnected
console.log(`Attempts: ${conn.getAttempts()}`); // => Output: Attempts: 0

Key Takeaway: Context object stores shared data (attempts counter) that states read and modify. States make decisions based on accumulated context data, enabling stateful behavior beyond simple state transitions.

Why It Matters: Context data enables retry logic, rate limiting, and circuit breakers. Without context, states are stateless - you can't track "failed multiple times" because counter isn't shared. SDK retry logic uses context-based retry: after multiple failed attempts (tracked in context), FSM transitions to Backoff state with exponential delay. Context data makes FSMs adapt to history, not just current state.

Example 53: State Pattern with Strategy

State Pattern can compose with Strategy Pattern - states can select different strategies for executing behavior.

TypeScript Implementation:

// State + Strategy Pattern composition
interface PaymentStrategy {
  // => Type declaration defines structure
  pay(amount: number): void; // => Strategy: how to pay
}
 
class CreditCardStrategy implements PaymentStrategy {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  pay(amount: number): void {
    console.log(`Paid $${amount} via Credit Card`); // => Output for verification
    // => Debug/audit output
    // => Log for observability
  }
}
 
class PayPalStrategy implements PaymentStrategy {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  pay(amount: number): void {
    console.log(`Paid $${amount} via PayPal`); // => Output for verification
    // => Debug/audit output
    // => Log for observability
  }
}
 
interface CheckoutState {
  // => Type declaration defines structure
  handle(event: string, context: CheckoutProcess): void;
  // => Event handler: main FSM dispatch method
  getName(): string;
}
 
class SelectingPaymentState implements CheckoutState {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private strategy: PaymentStrategy | null = null; // => Selected payment strategy
  // => Initialized alongside FSM state
 
  handle(event: string, context: CheckoutProcess): void {
    // => Event handler: main FSM dispatch method
    if (event === "selectCreditCard") {
      // => Event type guard condition
      // => Event type check
      this.strategy = new CreditCardStrategy(); // => Choose strategy
      console.log("Payment method: Credit Card"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      context.setPaymentStrategy(this.strategy); // => Save to context
      context.transitionTo(new ProcessingPaymentState());
      // => Constructor creates new object instance
    } else if (event === "selectPayPal") {
      this.strategy = new PayPalStrategy();
      // => Constructor creates new object instance
      // => Create new instance
      console.log("Payment method: PayPal"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      context.setPaymentStrategy(this.strategy);
      context.transitionTo(new ProcessingPaymentState());
      // => Constructor creates new object instance
    }
  }
 
  getName(): string {
    return "SelectingPayment"; // => Returns value to caller
    // => Return computed result
  }
}
 
class ProcessingPaymentState implements CheckoutState {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  handle(event: string, context: CheckoutProcess): void {
    // => Event handler: main FSM dispatch method
    if (event === "confirm") {
      // => Event type guard condition
      // => Event type check
      const strategy = context.getPaymentStrategy(); // => Retrieve strategy
      if (strategy) {
        // => Conditional branch
        // => Conditional check
        // => Branch execution based on condition
        strategy.pay(context.getAmount()); // => Execute payment via strategy
        context.transitionTo(new CompletedState());
        // => Constructor creates new object instance
      }
    } else if (event === "cancel") {
      context.transitionTo(new SelectingPaymentState()); // => Back to selection
    }
  }
 
  getName(): string {
    return "ProcessingPayment"; // => Returns value to caller
    // => Return computed result
  }
}
 
class CompletedState implements CheckoutState {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  handle(event: string, context: CheckoutProcess): void {
    // => Event handler: main FSM dispatch method
    console.log("Checkout complete. No further actions."); // => Output for verification
    // => Debug/audit output
    // => Log for observability
  }
 
  getName(): string {
    return "Completed"; // => Returns value to caller
    // => Return computed result
  }
}
 
class CheckoutProcess {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: CheckoutState; // => State field: stores current FSM state privately
  // => State variable declaration
  private paymentStrategy: PaymentStrategy | null = null; // => Context: selected strategy
  // => Initialized alongside FSM state
  private amount: number; // => Field declaration: class member variable
  // => Extended state (data beyond FSM state)
 
  constructor(amount: number) {
    this.amount = amount;
    this.state = new SelectingPaymentState(); // => State transition execution
    // => Constructor creates new object instance
    // => Create new instance
  }
 
  getAmount(): number {
    return this.amount; // => Returns value to caller
    // => Return computed result
  }
 
  setPaymentStrategy(strategy: PaymentStrategy): void {
    this.paymentStrategy = strategy;
  }
 
  getPaymentStrategy(): PaymentStrategy | null {
    return this.paymentStrategy; // => Returns value to caller
    // => Return computed result
  }
 
  transitionTo(newState: CheckoutState): void {
    console.log(`Transition: ${this.state.getName()} → ${newState.getName()}`); // => Output for verification
    // => Chained method calls or nested operations
    // => Debug/audit output
    // => Log for observability
    this.state = newState; // => State transition execution
  }
 
  handleEvent(event: string): void {
    // => Event handler: main FSM dispatch method
    this.state.handle(event, this);
    // => Event handler: main FSM dispatch method
  }
 
  getCurrentState(): string {
    // => Query method: read current FSM state
    return this.state.getName(); // => Returns value to caller
    // => Return current state value
  }
}
 
// Usage
const checkout = new CheckoutProcess(100); // => amount: $100, state: SelectingPayment
 
checkout.handleEvent("selectCreditCard"); // => Choose Credit Card strategy
// => Output: Payment method: Credit Card
// =>         Transition: SelectingPayment → ProcessingPayment
 
checkout.handleEvent("confirm"); // => Execute payment via Credit Card strategy
// => Output: Paid $100 via Credit Card
// =>         Transition: ProcessingPayment → Completed
 
// Alternative flow with PayPal
const checkout2 = new CheckoutProcess(200); // => Instance creation via constructor
// => Create new instance
// => Initialize checkout2
checkout2.handleEvent("selectPayPal"); // => Choose PayPal strategy
// => Output: Payment method: PayPal
// =>         Transition: SelectingPayment → ProcessingPayment
 
checkout2.handleEvent("confirm"); // => Execute payment via PayPal strategy
// => Output: Paid $200 via PayPal
// =>         Transition: ProcessingPayment → Completed

Key Takeaway: State Pattern can compose with Strategy Pattern - states select strategies, and context stores the selected strategy. This separates state transitions (State Pattern) from behavior execution (Strategy Pattern).

Why It Matters: Composing patterns provides flexibility. E-commerce checkouts need State Pattern for workflow (selecting payment → processing → complete) and Strategy Pattern for payment methods (credit card, PayPal, crypto). Without composition, you'd duplicate payment logic across states or duplicate workflow logic across payment methods. Stripe's checkout FSM uses this composition - 5 states (select/process/authorize/capture/complete) × 20 payment strategies = clean separation instead of 100 state-strategy combinations.

Production Workflows (Examples 54-60)

Example 54: Order Processing FSM - Basic Flow

A production order processing FSM handles the complete order lifecycle from creation to completion.

stateDiagram-v2
    [*] --> Draft
    Draft --> Submitted: submit
    Submitted --> Confirmed: confirm
    Confirmed --> Processing: startProcessing
    Processing --> Shipped: ship
    Shipped --> Delivered: deliver
    Delivered --> [*]
 
    Submitted --> Cancelled: cancel
    Confirmed --> Cancelled: cancel
    Processing --> Cancelled: cancel
    Cancelled --> [*]
 
    classDef draftState fill:#0173B2,stroke:#000,color:#fff
    classDef activeState fill:#029E73,stroke:#000,color:#fff
    classDef terminalState fill:#DE8F05,stroke:#000,color:#fff
    classDef cancelledState fill:#CC78BC,stroke:#000,color:#fff
 
    class Draft draftState
    class Submitted,Confirmed,Processing,Shipped activeState
    class Delivered terminalState
    class Cancelled cancelledState

TypeScript Implementation:

// Order Processing FSM
type OrderState = "Draft" | "Submitted" | "Confirmed" | "Processing" | "Shipped" | "Delivered" | "Cancelled"; // => Type declaration defines structure
// => Enum-like union type for state values
// => Type system ensures only valid states used
type OrderEvent = "submit" | "confirm" | "startProcessing" | "ship" | "deliver" | "cancel"; // => Type declaration defines structure
// => Defines event alphabet for FSM
// => Events trigger state transitions
 
interface Order {
  // => Type declaration defines structure
  id: string;
  items: string[];
  total: number;
}
 
class OrderProcessor {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: OrderState = "Draft"; // => State field: stores current FSM state privately
  // => Mutable state storage (single source of truth)
  // => FSM begins execution in Draft state
  private order: Order; // => Field declaration: class member variable
  // => Extended state (data beyond FSM state)
 
  constructor(order: Order) {
    this.order = order;
    console.log(`Order ${order.id} created in Draft state`); // => Output for verification
    // => Debug/audit output
    // => Log for observability
  }
 
  getCurrentState(): OrderState {
    // => Query method: read current FSM state
    return this.state; // => Returns value to caller
    // => Return current state value
  }
 
  getOrder(): Order {
    return this.order; // => Returns value to caller
    // => Return computed result
  }
 
  handleEvent(event: OrderEvent): void {
    // => Event handler: main FSM dispatch method
    const prevState = this.state; // => State variable initialization
    // => Initialize prevState
 
    if (event === "submit" && this.state === "Draft") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Event type check
      // => Combined (state, event) guard
      this.state = "Submitted"; // => State transition execution
      // => Transition: set state to Submitted
      // => State mutation (core FSM operation)
      console.log(`Order submitted: ${this.order.items.length} items, $${this.order.total}`); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    } else if (event === "confirm" && this.state === "Submitted") {
      // => Logical AND: both conditions must be true
      this.state = "Confirmed"; // => State transition execution
      // => Transition: set state to Confirmed
      // => State mutation (core FSM operation)
      console.log("Order confirmed: Payment authorized"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    } else if (event === "startProcessing" && this.state === "Confirmed") {
      // => Logical AND: both conditions must be true
      this.state = "Processing"; // => State transition execution
      // => Transition: set state to Processing
      // => State mutation (core FSM operation)
      console.log("Processing: Preparing items for shipment"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    } else if (event === "ship" && this.state === "Processing") {
      // => Logical AND: both conditions must be true
      this.state = "Shipped"; // => State transition execution
      // => Transition: set state to Shipped
      // => State mutation (core FSM operation)
      console.log("Order shipped: Tracking number assigned"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    } else if (event === "deliver" && this.state === "Shipped") {
      // => Logical AND: both conditions must be true
      this.state = "Delivered"; // => State transition execution
      // => Transition: set state to Delivered
      // => State mutation (core FSM operation)
      console.log("Order delivered: Customer signed"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    } else if (event === "cancel" && ["Submitted", "Confirmed", "Processing"].includes(this.state)) {
      // => Logical AND: both conditions must be true
      this.state = "Cancelled"; // => State transition execution
      // => Transition: set state to Cancelled
      // => State mutation (core FSM operation)
      console.log(`Order cancelled from ${prevState} state`); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    } else {
      // => Fallback branch
      throw new Error(`Invalid transition: ${event} not allowed in ${this.state} state`);
      // => Constructor creates new object instance
      // => Reject invalid operation
      // => Fail fast on FSM violation
    }
 
    console.log(`Transition: ${prevState} → ${this.state}`); // => Output for verification
    // => Debug/audit output
    // => Log for observability
  }
}
 
// Usage
const order = new OrderProcessor({
  // => Instance creation via constructor
  // => Create new instance
  // => Initialize order
  id: "ORD-001",
  items: ["Widget A", "Widget B"],
  total: 99.99,
});
// => Output: Order ORD-001 created in Draft state
 
order.handleEvent("submit"); // => Draft → Submitted
// => Output: Order submitted: 2 items, $99.99
// =>         Transition: Draft → Submitted
 
order.handleEvent("confirm"); // => Submitted → Confirmed
// => Output: Order confirmed: Payment authorized
// =>         Transition: Submitted → Confirmed
 
order.handleEvent("startProcessing"); // => Confirmed → Processing
// => Output: Processing: Preparing items for shipment
// =>         Transition: Confirmed → Processing
 
order.handleEvent("cancel"); // => Processing → Cancelled
// => Output: Order cancelled from Processing state
// =>         Transition: Processing → Cancelled
 
console.log(`Final state: ${order.getCurrentState()}`); // => Output: Final state: Cancelled

Key Takeaway: Production order FSMs handle happy path (Draft → Delivered) and cancellation path (cancel from Submitted/Confirmed/Processing). Invalid transitions throw errors, ensuring order integrity.

Why It Matters: Order FSMs prevent invalid operations like shipping an unconfirmed order or delivering before shipping. Order systems with FSMs eliminate "order in impossible state" bugs (like "cancelled but also shipped"). FSMs make order lifecycle auditable - every state transition is logged, satisfying financial compliance requirements. The immutable audit trail produced by FSM transitions also enables customer service to give precise answers about what happened to any order at any point in time.

Example 55: Order Processing with Inventory Checks

Production orders integrate with inventory, using guards to validate stock before state transitions.

TypeScript Implementation:

// Order FSM with inventory validation
type OrderState = "Draft" | "Submitted" | "Confirmed" | "Shipped" | "OutOfStock"; // => Type declaration defines structure
// => Enum-like union type for state values
// => Type system ensures only valid states used
type OrderEvent = "submit" | "confirm" | "ship"; // => Type declaration defines structure
// => Defines event alphabet for FSM
// => Events trigger state transitions
 
interface OrderItem {
  // => Type declaration defines structure
  sku: string;
  quantity: number;
}
 
class InventorySystem {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private stock: Record<string, number> = {
    // => Field declaration: class member variable
    // => Extended state (data beyond FSM state)
    // => Initialized alongside FSM state
    "SKU-A": 10,
    "SKU-B": 5,
    "SKU-C": 0, // => Out of stock
  };
 
  checkAvailability(items: OrderItem[]): boolean {
    for (const item of items) {
      // => Iterate collection
      const available = this.stock[item.sku] || 0; // => Check stock
      if (available < item.quantity) {
        // => Conditional branch
        // => Conditional check
        // => Branch execution based on condition
        console.log(`Out of stock: ${item.sku} (need ${item.quantity}, have ${available})`); // => Output for verification
        // => Chained method calls or nested operations
        // => Debug/audit output
        // => Log for observability
        return false; // => Guard fails: insufficient stock
      }
    }
    return true; // => All items available
  }
 
  reserveStock(items: OrderItem[]): void {
    for (const item of items) {
      // => Iterate collection
      this.stock[item.sku] -= item.quantity; // => Reserve inventory
      console.log(`Reserved: ${item.quantity} × ${item.sku} (remaining: ${this.stock[item.sku]})`); // => Output for verification
      // => Chained method calls or nested operations
      // => Debug/audit output
      // => Log for observability
    }
  }
}
 
class InventoryAwareOrder {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: OrderState = "Draft"; // => State field: stores current FSM state privately
  // => Mutable state storage (single source of truth)
  // => FSM begins execution in Draft state
  private items: OrderItem[]; // => Field declaration: class member variable
  // => Extended state (data beyond FSM state)
  private inventory: InventorySystem; // => Field declaration: class member variable
  // => Extended state (data beyond FSM state)
 
  constructor(items: OrderItem[], inventory: InventorySystem) {
    this.items = items;
    this.inventory = inventory;
  }
 
  getCurrentState(): OrderState {
    // => Query method: read current FSM state
    return this.state; // => Returns value to caller
    // => Return current state value
  }
 
  handleEvent(event: OrderEvent): void {
    // => Event handler: main FSM dispatch method
    if (event === "submit" && this.state === "Draft") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Event type check
      // => Combined (state, event) guard
      this.state = "Submitted"; // => Draft → Submitted (no guard)
      console.log("Order submitted"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    } else if (event === "confirm" && this.state === "Submitted") {
      // => Logical AND: both conditions must be true
      // Guard: Check inventory before confirming
      if (this.inventory.checkAvailability(this.items)) {
        // => Conditional branch
        // => Chained method calls or nested operations
        // => Conditional check
        // => Branch execution based on condition
        this.inventory.reserveStock(this.items); // => Reserve stock
        this.state = "Confirmed"; // => Submitted → Confirmed
        console.log("Order confirmed: Stock reserved"); // => Output for verification
        // => Debug/audit output
        // => Log for observability
      } else {
        // => Fallback branch
        this.state = "OutOfStock"; // => Guard failed → OutOfStock
        console.log("Order failed: Insufficient inventory"); // => Output for verification
        // => Debug/audit output
        // => Log for observability
      }
    } else if (event === "ship" && this.state === "Confirmed") {
      // => Logical AND: both conditions must be true
      this.state = "Shipped"; // => Confirmed → Shipped
      console.log("Order shipped"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    } else {
      // => Fallback branch
      console.log(`Invalid transition: ${event} in ${this.state}`); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    }
  }
}
 
// Usage - Successful order
const inventory = new InventorySystem(); // => Instance creation via constructor
// => Create new instance
// => Initialize inventory
const successOrder = new InventoryAwareOrder( // => Instance creation via constructor
  // => Create new instance
  // => Initialize successOrder
  [
    { sku: "SKU-A", quantity: 2 },
    { sku: "SKU-B", quantity: 1 },
  ],
  inventory,
);
 
successOrder.handleEvent("submit"); // => Draft → Submitted
// => Output: Order submitted
 
successOrder.handleEvent("confirm"); // => Check inventory (passes)
// => Output: Reserved: 2 × SKU-A (remaining: 8)
// =>         Reserved: 1 × SKU-B (remaining: 4)
// =>         Order confirmed: Stock reserved
 
console.log(successOrder.getCurrentState()); // => Output: Confirmed
 
// Usage - Out of stock order
const failOrder = new InventoryAwareOrder([{ sku: "SKU-C", quantity: 1 }], inventory); // => Instance creation via constructor
// => Create new instance
// => Initialize failOrder
 
failOrder.handleEvent("submit"); // => Draft → Submitted
failOrder.handleEvent("confirm"); // => Check inventory (fails)
// => Output: Out of stock: SKU-C (need 1, have 0)
// =>         Order failed: Insufficient inventory
 
console.log(failOrder.getCurrentState()); // => Output: OutOfStock

Key Takeaway: Production FSMs integrate with external systems (inventory) via guards. Transitions succeed only if external conditions (stock availability) are met, preventing invalid state progressions.

Why It Matters: Inventory guards prevent overselling. Without FSM-integrated checks, orders could confirm even when out of stock, creating customer service nightmares. Shopify's order FSM checks inventory atomically during confirmation - if stock depletes between submission and confirmation (race condition), guard fails and order enters "Awaiting Restock" state instead of confirming. This guard-based approach eliminates the need for compensating transactions to cancel oversold orders, preventing the customer experience damage of accepting then cancelling orders.

Example 56: Order Processing with Timeout Handling

Production orders handle timeouts - orders stuck in intermediate states too long should auto-transition (payment timeout, processing timeout).

TypeScript Implementation:

// Order FSM with timeouts
type OrderState = "Submitted" | "PaymentPending" | "Confirmed" | "Expired"; // => Type declaration defines structure
// => Enum-like union type for state values
// => Type system ensures only valid states used
type OrderEvent = "authorize" | "timeout"; // => Type declaration defines structure
// => Defines event alphabet for FSM
// => Events trigger state transitions
 
class TimedOrder {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: OrderState = "Submitted"; // => State field: stores current FSM state privately
  // => Mutable state storage (single source of truth)
  // => FSM begins execution in Submitted state
  private createdAt: number; // => Field declaration: class member variable
  // => Extended state (data beyond FSM state)
  private readonly PAYMENT_TIMEOUT = 5000; // => 5 seconds (milliseconds)
  // => Initialized alongside FSM state
  private timeoutHandle: NodeJS.Timeout | null = null; // => Field declaration: class member variable
  // => Extended state (data beyond FSM state)
  // => Initialized alongside FSM state
 
  constructor() {
    this.createdAt = Date.now();
    this.startPaymentTimeout(); // => Start timeout on creation
  }
 
  getCurrentState(): OrderState {
    // => Query method: read current FSM state
    return this.state; // => Returns value to caller
    // => Return current state value
  }
 
  private startPaymentTimeout(): void {
    // => Extended state (data beyond FSM state)
    this.timeoutHandle = setTimeout(() => {
      // => Chained method calls or nested operations
      if (this.state === "PaymentPending" || this.state === "Submitted") {
        // => State-based guard condition
        // => Logical OR: either condition can be true
        // => Conditional check
        // => Branch execution based on condition
        console.log("Payment timeout: No authorization received"); // => Output for verification
        // => Debug/audit output
        // => Log for observability
        this.handleEvent("timeout"); // => Auto-trigger timeout event
      }
    }, this.PAYMENT_TIMEOUT);
    console.log(`Payment timeout scheduled (${this.PAYMENT_TIMEOUT}ms)`); // => Output for verification
    // => Chained method calls or nested operations
    // => Debug/audit output
    // => Log for observability
  }
 
  private clearTimeoutIfNeeded(): void {
    // => Extended state (data beyond FSM state)
    if (this.timeoutHandle) {
      // => Conditional check
      // => Branch execution based on condition
      clearTimeout(this.timeoutHandle); // => Cancel timeout
      this.timeoutHandle = null;
      console.log("Timeout cancelled"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    }
  }
 
  handleEvent(event: OrderEvent): void {
    // => Event handler: main FSM dispatch method
    if (event === "authorize" && this.state === "Submitted") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Event type check
      // => Combined (state, event) guard
      this.state = "PaymentPending"; // => Submitted → PaymentPending
      console.log("Payment authorization started"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    } else if (event === "authorize" && this.state === "PaymentPending") {
      // => Logical AND: both conditions must be true
      this.clearTimeoutIfNeeded(); // => Stop timeout
      this.state = "Confirmed"; // => PaymentPending → Confirmed
      console.log("Payment confirmed"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    } else if (event === "timeout" && ["Submitted", "PaymentPending"].includes(this.state)) {
      // => Logical AND: both conditions must be true
      this.clearTimeoutIfNeeded();
      this.state = "Expired"; // => Timeout → Expired
      console.log("Order expired: Payment not completed in time"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    }
  }
 
  cleanup(): void {
    this.clearTimeoutIfNeeded(); // => Cleanup resources
  }
}
 
// Usage - Successful payment within timeout
const quickOrder = new TimedOrder(); // => state: Submitted, timeout scheduled
// => Output: Payment timeout scheduled (5000ms)
 
quickOrder.handleEvent("authorize"); // => Submitted → PaymentPending
// => Output: Payment authorization started
 
// Authorize within timeout
setTimeout(() => {
  // => Chained method calls or nested operations
  quickOrder.handleEvent("authorize"); // => PaymentPending → Confirmed
  // => Output: Timeout cancelled
  // =>         Payment confirmed
  console.log(`State: ${quickOrder.getCurrentState()}`); // => Output: State: Confirmed
  quickOrder.cleanup();
}, 2000);
 
// Usage - Timeout expires
const slowOrder = new TimedOrder(); // => Instance creation via constructor
// => Create new instance
// => Initialize slowOrder
console.log("Waiting for timeout to expire..."); // => Output for verification
// => Debug/audit output
// => Log for observability
// After 5 seconds, timeout fires automatically:
// => Output: Payment timeout: No authorization received
// =>         Order expired: Payment not completed in time
setTimeout(() => {
  // => Chained method calls or nested operations
  console.log(`State: ${slowOrder.getCurrentState()}`); // => Output: State: Expired
  slowOrder.cleanup();
}, 6000);

Key Takeaway: Production FSMs handle timeouts using scheduled events that auto-trigger transitions if intermediate states persist too long. Timeouts prevent orders from getting stuck indefinitely.

Why It Matters: Timeout handling prevents resource leaks and improves UX. Payment providers reserve funds during authorization - if order never completes, funds stay reserved indefinitely, frustrating customers. Stripe's payment FSM expires authorizations after 7 days, auto-transitioning to "Expired" state and releasing reserved funds. Timeout FSMs clean up abandoned carts, stale sessions, and orphaned resources.

Example 57: Order Processing with Compensation (Saga Pattern)

Production orders implement compensation - if a step fails, FSM executes compensating transactions to undo previous steps.

TypeScript Implementation:

// Order FSM with compensation (Saga pattern)
type SagaState = "Initial" | "InventoryReserved" | "PaymentCharged" | "Completed" | "Compensating" | "Failed"; // => Type declaration defines structure
// => Enum-like union type for state values
// => Type system ensures only valid states used
type SagaEvent = "reserveInventory" | "chargePayment" | "complete" | "fail"; // => Type declaration defines structure
// => Defines event alphabet for FSM
// => Events trigger state transitions
 
class OrderSaga {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: SagaState = "Initial"; // => State field: stores current FSM state privately
  // => Mutable state storage (single source of truth)
  // => FSM begins execution in Initial state
  private compensations: Array<() => void> = []; // => Stack of compensating actions
  // => Initialized alongside FSM state
 
  getCurrentState(): SagaState {
    // => Query method: read current FSM state
    return this.state; // => Returns value to caller
    // => Return current state value
  }
 
  private recordCompensation(action: () => void): void {
    // => Chained method calls or nested operations
    // => Extended state (data beyond FSM state)
    // => Initialized alongside FSM state
    this.compensations.push(action); // => Add compensating action to stack
    console.log(`Compensation recorded (total: ${this.compensations.length})`); // => Output for verification
    // => Chained method calls or nested operations
    // => Debug/audit output
    // => Log for observability
  }
 
  private runCompensations(): void {
    // => Extended state (data beyond FSM state)
    console.log("Running compensations..."); // => Output for verification
    // => Debug/audit output
    // => Log for observability
    // Execute compensations in reverse order (LIFO)
    while (this.compensations.length > 0) {
      // => Loop while condition true
      const compensate = this.compensations.pop()!; // => Get last compensation
      compensate(); // => Execute compensation
    }
  }
 
  handleEvent(event: SagaEvent): void {
    // => Event handler: main FSM dispatch method
    if (event === "reserveInventory" && this.state === "Initial") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Event type check
      // => Combined (state, event) guard
      console.log("Step 1: Reserving inventory..."); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      // Simulate inventory reservation
      const reserved = true; // => Success
 
      if (reserved) {
        // => Conditional branch
        // => Conditional check
        // => Branch execution based on condition
        this.state = "InventoryReserved"; // => Initial → InventoryReserved
        this.recordCompensation(() => {
          // => Chained method calls or nested operations
          console.log("  Compensating: Releasing inventory reservation"); // => Output for verification
          // => Debug/audit output
          // => Log for observability
        });
        console.log("Inventory reserved"); // => Output for verification
        // => Debug/audit output
        // => Log for observability
      } else {
        // => Fallback branch
        this.handleEvent("fail"); // => Trigger failure
      }
    } else if (event === "chargePayment" && this.state === "InventoryReserved") {
      // => Logical AND: both conditions must be true
      console.log("Step 2: Charging payment..."); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      const charged = false; // => Simulate payment failure
 
      if (charged) {
        // => Conditional branch
        // => Conditional check
        // => Branch execution based on condition
        this.state = "PaymentCharged"; // => State transition execution
        // => Transition: set state to PaymentCharged
        // => State mutation (core FSM operation)
        this.recordCompensation(() => {
          // => Chained method calls or nested operations
          console.log("  Compensating: Refunding payment"); // => Output for verification
          // => Debug/audit output
          // => Log for observability
        });
        console.log("Payment charged"); // => Output for verification
        // => Debug/audit output
        // => Log for observability
      } else {
        // => Fallback branch
        console.log("Payment failed!"); // => Output for verification
        // => Debug/audit output
        // => Log for observability
        this.handleEvent("fail"); // => Trigger failure (will compensate)
      }
    } else if (event === "complete" && this.state === "PaymentCharged") {
      // => Logical AND: both conditions must be true
      this.state = "Completed"; // => PaymentCharged → Completed
      this.compensations = []; // => Clear compensations (saga succeeded)
      console.log("Order completed successfully"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    } else if (event === "fail") {
      this.state = "Compensating"; // => Enter compensating state
      this.runCompensations(); // => Execute all compensations
      this.state = "Failed"; // => Compensating → Failed
      console.log("Order failed: All compensations executed"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    }
  }
}
 
// Usage - Payment fails, triggers compensation
const saga = new OrderSaga(); // => state: Initial
 
saga.handleEvent("reserveInventory"); // => Initial → InventoryReserved
// => Output: Step 1: Reserving inventory...
// =>         Compensation recorded (total: 1)
// =>         Inventory reserved
 
saga.handleEvent("chargePayment"); // => Payment fails → Compensate
// => Output: Step 2: Charging payment...
// =>         Payment failed!
// =>         Running compensations...
// =>           Compensating: Releasing inventory reservation
// =>         Order failed: All compensations executed
 
console.log(`Final state: ${saga.getCurrentState()}`); // => Output: Final state: Failed

Key Takeaway: Saga pattern tracks compensating actions for each successful step. If any step fails, FSM executes compensations in reverse order (LIFO), undoing previous steps.

Why It Matters: Compensation enables distributed transaction rollback. Microservices can't use database transactions - order service reserves inventory, payment service charges card, shipping service creates shipment. If shipping fails, you must compensate: refund payment, release inventory. Order sagas use compensation: if restaurant rejects order after payment, saga compensates by refunding customer and releasing driver assignment. Without compensation, partial failures leave system in inconsistent state.

Example 58: Authentication Flow FSM - Login/Logout

Production authentication flows model login, session management, and logout as state transitions.

stateDiagram-v2
    [*] --> LoggedOut
    LoggedOut --> Authenticating: login
    Authenticating --> LoggedIn: success
    Authenticating --> LoggedOut: failure
    LoggedIn --> RefreshingToken: tokenExpiring
    RefreshingToken --> LoggedIn: tokenRefreshed
    RefreshingToken --> LoggedOut: refreshFailed
    LoggedIn --> LoggedOut: logout
 
    classDef loggedOutState fill:#0173B2,stroke:#000,color:#fff
    classDef authState fill:#DE8F05,stroke:#000,color:#fff
    classDef loggedInState fill:#029E73,stroke:#000,color:#fff
 
    class LoggedOut loggedOutState
    class Authenticating,RefreshingToken authState
    class LoggedIn loggedInState

TypeScript Implementation:

// Authentication FSM
type AuthState = "LoggedOut" | "Authenticating" | "LoggedIn" | "RefreshingToken"; // => Type declaration defines structure
// => Enum-like union type for state values
// => Type system ensures only valid states used
type AuthEvent = "login" | "success" | "failure" | "tokenExpiring" | "tokenRefreshed" | "refreshFailed" | "logout"; // => Type declaration defines structure
// => Defines event alphabet for FSM
// => Events trigger state transitions
 
interface User {
  // => Type declaration defines structure
  id: string;
  token: string;
  refreshToken: string;
}
 
class AuthenticationFlow {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: AuthState = "LoggedOut"; // => State field: stores current FSM state privately
  // => Mutable state storage (single source of truth)
  // => FSM begins execution in LoggedOut state
  private user: User | null = null; // => Field declaration: class member variable
  // => Extended state (data beyond FSM state)
  // => Initialized alongside FSM state
  private tokenRefreshHandle: NodeJS.Timeout | null = null; // => Field declaration: class member variable
  // => Extended state (data beyond FSM state)
  // => Initialized alongside FSM state
 
  getCurrentState(): AuthState {
    // => Query method: read current FSM state
    return this.state; // => Returns value to caller
    // => Return current state value
  }
 
  getUser(): User | null {
    return this.user; // => Returns value to caller
    // => Return computed result
  }
 
  private scheduleTokenRefresh(): void {
    // => Extended state (data beyond FSM state)
    this.tokenRefreshHandle = setTimeout(() => {
      // => Chained method calls or nested operations
      console.log("Token expiring soon..."); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      this.handleEvent("tokenExpiring"); // => Auto-trigger refresh
    }, 10000);
    console.log("Token refresh scheduled (10s)"); // => Output for verification
    // => Chained method calls or nested operations
    // => Debug/audit output
    // => Log for observability
  }
 
  private clearTokenRefresh(): void {
    // => Extended state (data beyond FSM state)
    if (this.tokenRefreshHandle) {
      // => Conditional check
      // => Branch execution based on condition
      clearTimeout(this.tokenRefreshHandle);
      this.tokenRefreshHandle = null;
    }
  }
 
  handleEvent(event: AuthEvent, data?: any): void {
    // => Ternary: condition ? true_branch : false_branch
    // => Event handler: main FSM dispatch method
    if (event === "login" && this.state === "LoggedOut") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Event type check
      // => Combined (state, event) guard
      this.state = "Authenticating"; // => LoggedOut → Authenticating
      console.log("Authenticating user..."); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      // Simulate async authentication
      setTimeout(() => {
        // => Chained method calls or nested operations
        const success = true; // => Simulate success
        if (success) {
          // => Conditional branch
          // => Conditional check
          // => Branch execution based on condition
          this.handleEvent("success", {
            // => Event handler: main FSM dispatch method
            id: "user123",
            token: "jwt-token",
            refreshToken: "refresh-token",
          });
        } else {
          // => Fallback branch
          this.handleEvent("failure");
          // => Event handler: main FSM dispatch method
        }
      }, 1000);
    } else if (event === "success" && this.state === "Authenticating") {
      // => Logical AND: both conditions must be true
      this.user = data; // => Store user data
      this.state = "LoggedIn"; // => Authenticating → LoggedIn
      console.log(`Login successful: User ${this.user!.id}`); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      this.scheduleTokenRefresh(); // => Start token refresh timer
    } else if (event === "failure" && this.state === "Authenticating") {
      // => Logical AND: both conditions must be true
      this.state = "LoggedOut"; // => Authenticating → LoggedOut (failed)
      console.log("Authentication failed"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    } else if (event === "tokenExpiring" && this.state === "LoggedIn") {
      // => Logical AND: both conditions must be true
      this.state = "RefreshingToken"; // => LoggedIn → RefreshingToken
      console.log("Refreshing authentication token..."); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      // Simulate token refresh
      setTimeout(() => {
        // => Chained method calls or nested operations
        const refreshSuccess = true; // => Simulate success
        if (refreshSuccess) {
          // => Conditional branch
          // => Conditional check
          // => Branch execution based on condition
          this.handleEvent("tokenRefreshed", { token: "new-jwt-token" });
          // => Event handler: main FSM dispatch method
        } else {
          // => Fallback branch
          this.handleEvent("refreshFailed");
          // => Event handler: main FSM dispatch method
        }
      }, 1000);
    } else if (event === "tokenRefreshed" && this.state === "RefreshingToken") {
      // => Logical AND: both conditions must be true
      this.user!.token = data.token; // => Update token
      this.state = "LoggedIn"; // => RefreshingToken → LoggedIn
      console.log("Token refreshed successfully"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      this.scheduleTokenRefresh(); // => Reschedule next refresh
    } else if (event === "refreshFailed" && this.state === "RefreshingToken") {
      // => Logical AND: both conditions must be true
      this.user = null; // => Clear user
      this.clearTokenRefresh();
      this.state = "LoggedOut"; // => RefreshingToken → LoggedOut
      console.log("Token refresh failed: Session expired"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    } else if (event === "logout" && this.state === "LoggedIn") {
      // => Logical AND: both conditions must be true
      this.user = null; // => Clear user
      this.clearTokenRefresh(); // => Cancel token refresh
      this.state = "LoggedOut"; // => LoggedIn → LoggedOut
      console.log("User logged out"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    }
  }
 
  cleanup(): void {
    this.clearTokenRefresh(); // => Cleanup timers
  }
}
 
// Usage
const auth = new AuthenticationFlow(); // => state: LoggedOut
console.log(`Initial state: ${auth.getCurrentState()}`); // => Output: Initial state: LoggedOut
 
auth.handleEvent("login"); // => LoggedOut → Authenticating
// => Output: Authenticating user...
// After 1 second:
// => Output: Login successful: User user123
// =>         Token refresh scheduled (10s)
// After 10 seconds:
// => Output: Token expiring soon...
// =>         Refreshing authentication token...
// =>         Token refreshed successfully
// =>         Token refresh scheduled (10s)
 
// Manual logout
setTimeout(() => {
  // => Chained method calls or nested operations
  auth.handleEvent("logout"); // => LoggedIn → LoggedOut
  // => Output: User logged out
  console.log(`State: ${auth.getCurrentState()}`); // => Output: State: LoggedOut
  auth.cleanup();
}, 15000);

Key Takeaway: Authentication FSMs handle login flow, token refresh lifecycle, and logout. Automatic token refresh prevents session expiration, transitioning between LoggedIn and RefreshingToken states transparently.

Why It Matters: Auth FSMs prevent authentication bugs. Without FSM, apps often fail to refresh tokens (causing unexpected logouts) or refresh during logout (wasting API calls). OAuth FSMs auto-refresh tokens before expiration, transitioning to RefreshingToken state. If refresh fails (revoked token), FSM transitions to LoggedOut and redirects to login, preventing API errors from expired tokens.

Example 59: Multi-Factor Authentication Flow

Production auth flows support MFA, adding intermediate verification states before granting full access.

TypeScript Implementation:

// Multi-Factor Authentication FSM
type MFAState = "LoggedOut" | "PasswordVerified" | "AwaitingMFA" | "LoggedIn"; // => Type declaration defines structure
// => Enum-like union type for state values
// => Type system ensures only valid states used
type MFAEvent = "submitPassword" | "requestMFA" | "submitMFA" | "mfaSuccess" | "mfaFail" | "logout"; // => Type declaration defines structure
// => Defines event alphabet for FSM
// => Events trigger state transitions
 
class MFAFlow {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: MFAState = "LoggedOut"; // => State field: stores current FSM state privately
  // => Mutable state storage (single source of truth)
  // => FSM begins execution in LoggedOut state
  private userId: string | null = null; // => Field declaration: class member variable
  // => Extended state (data beyond FSM state)
  // => Initialized alongside FSM state
  private mfaAttempts = 0;
  // => Extended state (data beyond FSM state)
  // => Initialized alongside FSM state
  private readonly MAX_MFA_ATTEMPTS = 3;
  // => Extended state (data beyond FSM state)
  // => Initialized alongside FSM state
 
  getCurrentState(): MFAState {
    // => Query method: read current FSM state
    return this.state; // => Returns value to caller
    // => Return current state value
  }
 
  handleEvent(event: MFAEvent, data?: any): void {
    // => Ternary: condition ? true_branch : false_branch
    // => Event handler: main FSM dispatch method
    if (event === "submitPassword" && this.state === "LoggedOut") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Event type check
      // => Combined (state, event) guard
      // Verify password
      const passwordValid = true; // => Simulate success
      if (passwordValid) {
        // => Conditional branch
        // => Conditional check
        // => Branch execution based on condition
        this.userId = data.userId; // => Store user ID
        this.state = "PasswordVerified"; // => LoggedOut → PasswordVerified
        console.log("Password verified. MFA required."); // => Output for verification
        // => Debug/audit output
        // => Log for observability
      } else {
        // => Fallback branch
        console.log("Invalid password"); // => Output for verification
        // => Debug/audit output
        // => Log for observability
      }
    } else if (event === "requestMFA" && this.state === "PasswordVerified") {
      // => Logical AND: both conditions must be true
      this.state = "AwaitingMFA"; // => PasswordVerified → AwaitingMFA
      console.log("MFA code sent to device. Awaiting verification..."); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    } else if (event === "submitMFA" && this.state === "AwaitingMFA") {
      // => Logical AND: both conditions must be true
      const codeValid = data.code === "123456"; // => Simulate verification
 
      if (codeValid) {
        // => Conditional branch
        // => Conditional check
        // => Branch execution based on condition
        this.handleEvent("mfaSuccess");
        // => Event handler: main FSM dispatch method
      } else {
        // => Fallback branch
        this.mfaAttempts++; // => Track failed attempts
        console.log(`MFA failed. Attempts: ${this.mfaAttempts}/${this.MAX_MFA_ATTEMPTS}`); // => Output for verification
        // => Debug/audit output
        // => Log for observability
 
        if (this.mfaAttempts >= this.MAX_MFA_ATTEMPTS) {
          // => Conditional branch
          // => Conditional check
          // => Branch execution based on condition
          this.handleEvent("mfaFail"); // => Too many attempts
        } else {
          // => Fallback branch
          console.log("Try again."); // => Output for verification
          // => Debug/audit output
          // => Log for observability
        }
      }
    } else if (event === "mfaSuccess" && this.state === "AwaitingMFA") {
      // => Logical AND: both conditions must be true
      this.state = "LoggedIn"; // => AwaitingMFA → LoggedIn
      this.mfaAttempts = 0; // => Reset attempts
      console.log(`MFA successful. User ${this.userId} logged in.`); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    } else if (event === "mfaFail" && this.state === "AwaitingMFA") {
      // => Logical AND: both conditions must be true
      this.userId = null; // => Clear user
      this.mfaAttempts = 0; // => Reset attempts
      this.state = "LoggedOut"; // => AwaitingMFA → LoggedOut (lockout)
      console.log("MFA lockout: Too many failed attempts"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    } else if (event === "logout" && this.state === "LoggedIn") {
      // => Logical AND: both conditions must be true
      this.userId = null;
      this.state = "LoggedOut"; // => LoggedIn → LoggedOut
      console.log("User logged out"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    }
  }
}
 
// Usage - Successful MFA
const mfa = new MFAFlow(); // => state: LoggedOut
 
mfa.handleEvent("submitPassword", { userId: "user123" }); // => LoggedOut → PasswordVerified
// => Output: Password verified. MFA required.
 
mfa.handleEvent("requestMFA"); // => PasswordVerified → AwaitingMFA
// => Output: MFA code sent to device. Awaiting verification...
 
mfa.handleEvent("submitMFA", { code: "123456" }); // => Correct code → LoggedIn
// => Output: MFA successful. User user123 logged in.
console.log(`State: ${mfa.getCurrentState()}`); // => Output: State: LoggedIn
 
// Usage - Failed MFA (too many attempts)
const mfa2 = new MFAFlow(); // => Instance creation via constructor
// => Create new instance
// => Initialize mfa2
mfa2.handleEvent("submitPassword", { userId: "user456" });
// => Event handler: main FSM dispatch method
mfa2.handleEvent("requestMFA");
// => Event handler: main FSM dispatch method
 
mfa2.handleEvent("submitMFA", { code: "wrong1" }); // => Attempt 1
// => Output: MFA failed. Attempts: 1/3
mfa2.handleEvent("submitMFA", { code: "wrong2" }); // => Attempt 2
// => Output: MFA failed. Attempts: 2/3
mfa2.handleEvent("submitMFA", { code: "wrong3" }); // => Attempt 3 → Lockout
// => Output: MFA failed. Attempts: 3/3
// =>         MFA lockout: Too many failed attempts
console.log(`State: ${mfa2.getCurrentState()}`); // => Output: State: LoggedOut

Key Takeaway: MFA FSMs add intermediate verification states (PasswordVerified → AwaitingMFA) before full authentication. Failed attempts are tracked, triggering lockout after threshold.

Why It Matters: MFA FSMs enforce security policies. Without state tracking, apps might allow unlimited MFA attempts (brute force attack) or grant access after password verification alone (bypassing MFA). IAM systems use MFA FSMs: after multiple failed MFA attempts, account transitions to "Locked" state requiring admin intervention. FSM ensures MFA can't be bypassed by refreshing page or using multiple devices.

Example 60: Session Timeout and Re-authentication

Production auth flows handle session timeouts, requiring re-authentication for sensitive operations.

TypeScript Implementation:

// Session timeout with re-authentication
type SessionState = "LoggedOut" | "Active" | "Idle" | "RequiresReauth" | "Reauthenticating"; // => Type declaration defines structure
// => Enum-like union type for state values
// => Type system ensures only valid states used
type SessionEvent = "login" | "activity" | "timeout" | "sensitiveOp" | "reauth" | "reauthSuccess" | "logout"; // => Type declaration defines structure
// => Defines event alphabet for FSM
// => Events trigger state transitions
 
class SessionManager {
  // => State machine implementation class
  // => Encapsulates state + transition logic
  private state: SessionState = "LoggedOut"; // => State field: stores current FSM state privately
  // => Mutable state storage (single source of truth)
  // => FSM begins execution in LoggedOut state
  private activityTimeout: NodeJS.Timeout | null = null; // => Field declaration: class member variable
  // => Extended state (data beyond FSM state)
  // => Initialized alongside FSM state
  private readonly IDLE_TIMEOUT = 5000; // => 5 seconds (for demo)
  // => Initialized alongside FSM state
 
  getCurrentState(): SessionState {
    // => Query method: read current FSM state
    return this.state; // => Returns value to caller
    // => Return current state value
  }
 
  private resetActivityTimer(): void {
    // => Extended state (data beyond FSM state)
    this.clearActivityTimer(); // => Clear existing timer
    this.activityTimeout = setTimeout(() => {
      // => Chained method calls or nested operations
      console.log("Session idle timeout"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      this.handleEvent("timeout"); // => Auto-trigger timeout
    }, this.IDLE_TIMEOUT);
    console.log(`Activity timer reset (${this.IDLE_TIMEOUT}ms)`); // => Output for verification
    // => Chained method calls or nested operations
    // => Debug/audit output
    // => Log for observability
  }
 
  private clearActivityTimer(): void {
    // => Extended state (data beyond FSM state)
    if (this.activityTimeout) {
      // => Conditional branch
      // => Conditional check
      // => Branch execution based on condition
      clearTimeout(this.activityTimeout);
      this.activityTimeout = null;
    }
  }
 
  handleEvent(event: SessionEvent): void {
    // => Event handler: main FSM dispatch method
    if (event === "login" && this.state === "LoggedOut") {
      // => Event type guard condition
      // => Logical AND: both conditions must be true
      // => Event type check
      // => Combined (state, event) guard
      this.state = "Active"; // => LoggedOut → Active
      console.log("User logged in"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      this.resetActivityTimer(); // => Start idle tracking
    } else if (event === "activity" && this.state === "Active") {
      // => Logical AND: both conditions must be true
      console.log("User activity detected"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      this.resetActivityTimer(); // => Reset idle timer on activity
    } else if (event === "timeout" && this.state === "Active") {
      // => Logical AND: both conditions must be true
      this.state = "Idle"; // => Active → Idle (no activity)
      console.log("Session idle: Limited access"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      this.clearActivityTimer();
    } else if (event === "activity" && this.state === "Idle") {
      // => Logical AND: both conditions must be true
      this.state = "Active"; // => Idle → Active (activity resumes)
      console.log("Activity resumed: Session active"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      this.resetActivityTimer();
    } else if (event === "sensitiveOp" && this.state === "Active") {
      // => Logical AND: both conditions must be true
      this.state = "RequiresReauth"; // => Active → RequiresReauth
      console.log("Sensitive operation: Re-authentication required"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      this.clearActivityTimer();
    } else if (event === "sensitiveOp" && this.state === "Idle") {
      // => Logical AND: both conditions must be true
      this.state = "RequiresReauth"; // => Idle → RequiresReauth
      console.log("Sensitive operation from idle: Re-authentication required"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    } else if (event === "reauth" && this.state === "RequiresReauth") {
      // => Logical AND: both conditions must be true
      this.state = "Reauthenticating"; // => RequiresReauth → Reauthenticating
      console.log("Re-authenticating..."); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      // Simulate re-auth
      setTimeout(() => {
        // => Chained method calls or nested operations
        this.handleEvent("reauthSuccess");
        // => Event handler: main FSM dispatch method
      }, 1000);
    } else if (event === "reauthSuccess" && this.state === "Reauthenticating") {
      // => Logical AND: both conditions must be true
      this.state = "Active"; // => Reauthenticating → Active
      console.log("Re-authentication successful"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
      this.resetActivityTimer();
    } else if (event === "logout") {
      this.clearActivityTimer();
      this.state = "LoggedOut"; // => Any state → LoggedOut
      console.log("User logged out"); // => Output for verification
      // => Debug/audit output
      // => Log for observability
    }
  }
 
  cleanup(): void {
    this.clearActivityTimer();
  }
}
 
// Usage
const session = new SessionManager(); // => state: LoggedOut
 
session.handleEvent("login"); // => LoggedOut → Active
// => Output: User logged in
// =>         Activity timer reset (5000ms)
 
session.handleEvent("activity"); // => Reset idle timer
// => Output: User activity detected
// =>         Activity timer reset (5000ms)
 
// Wait for idle timeout
setTimeout(() => {
  // => Chained method calls or nested operations
  // After 5 seconds idle:
  // => Output: Session idle timeout
  // =>         Session idle: Limited access
  console.log(`State: ${session.getCurrentState()}`); // => Output: State: Idle
 
  // Try sensitive operation
  session.handleEvent("sensitiveOp"); // => Idle → RequiresReauth
  // => Output: Sensitive operation from idle: Re-authentication required
 
  session.handleEvent("reauth"); // => RequiresReauth → Reauthenticating
  // => Output: Re-authenticating...
  // After 1 second:
  // => Output: Re-authentication successful
  // =>         Activity timer reset (5000ms)
 
  setTimeout(() => {
    // => Chained method calls or nested operations
    session.handleEvent("logout");
    // => Event handler: main FSM dispatch method
    session.cleanup();
  }, 2000);
}, 6000);

Key Takeaway: Session FSMs track user activity, transitioning between Active (engaged) and Idle (no activity) states. Sensitive operations require re-authentication, transitioning to RequiresReauth state regardless of session activity.

Why It Matters: Session FSMs balance security and UX. Banking apps transition to Idle after inactivity, limiting access to viewing balance (read-only). Attempting a transfer (sensitive op) from Idle state requires re-authentication. This prevents unauthorized transfers if user leaves device unlocked. Code hosting systems use session FSMs: after extended idle time, session transitions to RequiresReauth - viewing repos is allowed (read), but pushing code (write) requires re-authentication.

Last updated January 30, 2026

Command Palette

Search for a command to run...