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.
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.
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 {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: State = "Disconnected"; // => Initial: Disconnected
// => FSM begins execution in Disconnected state
getCurrentState(): State {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns current state
}
handleEvent(event: Event): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
// 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
// => Comparison check
// => 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
// => Comparison check
// => 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") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
this.state = "Connected.Active"; // => Idle → Active (within Connected)
} else if (this.state === "Connected.Active" && event === "pause") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
this.state = "Connected.Idle"; // => Active → Idle (within Connected)
} else {
// => Statement execution
// => 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
// => Processes events, triggers transitions
console.log(conn.getCurrentState()); // => Output: Connected.Idle
// => FSM state management logic
// => Pure read, no side effects
conn.handleEvent("activity"); // => Connected.Idle → Connected.Active
// => Processes events, triggers transitions
console.log(conn.getCurrentState()); // => Output: Connected.Active
// => FSM state management logic
// => Pure read, no side effects
conn.handleEvent("disconnect"); // => Parent transition: Any Connected → Disconnected
// => Processes events, triggers transitions
console.log(conn.getCurrentState()); // => Output: Disconnected
// => FSM state management logic
// => Pure read, no side effects
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.).
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 {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: State = "Off"; // => Initial: Off
// => FSM begins execution in Off state
getCurrentState(): State {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns state
}
private onEnterParent(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log("→ Entered On state (parent)"); // => Parent entry action
// => FSM state management logic
// => Log for observability
// => Example: Initialize resources
}
private onExitParent(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log("← Exited On state (parent)"); // => Parent exit action
// => FSM state management logic
// => Log for observability
// => Example: Release resources
}
handleEvent(event: Event): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (this.state === "Off" && event === "powerOn") {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => 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") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
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") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
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") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
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") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
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)
// => FSM state management logic
// => Processes events, triggers transitions
// => Output: → Entered On state (parent)
// => → Entered Starting substate
machine.handleEvent("started"); // => On.Starting → On.Running (substate transition)
// => FSM state management logic
// => Processes events, triggers transitions
// => Output: ← Exited Starting substate
// => → Entered Running substate
machine.handleEvent("powerOff"); // => Emergency exit: any On substate → Off
// => Processes events, triggers transitions
// => 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 {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: State = "Offline"; // => Initial: Offline
// => FSM begins execution in Offline state
getCurrentState(): State {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns state
}
handleEvent(event: Event): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
// 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
// => Comparison check
// => 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; // => Statement execution
}
// Regular transitions
if (this.state === "Offline" && event === "connect") {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => 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") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
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") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
this.state = "Online.Authenticated.Purchasing"; // => Browsing → Purchasing (within Authenticated)
} else if (this.state === "Online.Authenticated.Purchasing" && event === "cancel") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
this.state = "Online.Authenticated.Browsing"; // => Purchasing → Browsing
} else {
// => Statement execution
// => 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
// => Processes events, triggers transitions
console.log(app.getCurrentState()); // => Output: Online.Guest
// => FSM state management logic
// => Pure read, no side effects
app.handleEvent("login"); // => Online.Guest → Online.Authenticated.Browsing (two levels deep)
// => FSM state management logic
// => Processes events, triggers transitions
console.log(app.getCurrentState()); // => Output: Online.Authenticated.Browsing
// => FSM state management logic
// => Pure read, no side effects
app.handleEvent("checkout"); // => Browsing → Purchasing (within Authenticated)
// => FSM state management logic
// => Processes events, triggers transitions
console.log(app.getCurrentState()); // => Output: Online.Authenticated.Purchasing
// => FSM state management logic
// => Pure read, no side effects
app.handleEvent("networkError"); // => Parent transition: exits ALL levels to Offline
// => Processes events, triggers transitions
console.log(app.getCurrentState()); // => Output: Offline
// => FSM state management logic
// => Pure read, no side effects
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
// => Begin object/config definition
defaultSubstate?: State; // => Default substate when entering parent
// => FSM state management logic
}
class VideoPlayer {
// => Class encapsulates FSM logic
// => 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
// => Assign value
// => Extended state (data beyond FSM state)
// => Initialized alongside FSM state
Playing: { defaultSubstate: "Playing.Loading" }, // => Playing defaults to Loading substate
// => FSM state management logic
};
getCurrentState(): State {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns state
}
handleEvent(event: Event): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (this.state === "Stopped" && event === "play") {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => Event type check
// => Combined (state, event) guard
const defaultSubstate = this.stateConfig["Playing"].defaultSubstate!; // => State variable initialization
// => Access instance property
// => 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") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
this.state = "Playing.Buffering"; // => Loading → Buffering
} else if (this.state === "Playing.Buffering" && event === "buffered") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
this.state = "Playing.Active"; // => Buffering → Active
} else if (this.state.startsWith("Playing.") && event === "pause") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
this.state = "Stopped"; // => Exit Playing from any substate
} else {
// => Statement execution
// => 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)
// => FSM state management logic
// => Processes events, triggers transitions
// => Output: Entered Playing at default: Playing.Loading
console.log(player.getCurrentState()); // => Output: Playing.Loading
// => FSM state management logic
// => Pure read, no side effects
player.handleEvent("loaded"); // => Playing.Loading → Playing.Buffering
// => Processes events, triggers transitions
console.log(player.getCurrentState()); // => Output: Playing.Buffering
// => FSM state management logic
// => Pure read, no side effects
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.
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 {
// => Class encapsulates FSM logic
// => 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 } {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return { audio: this.audioState, video: this.videoState }; // => Returns both region states
// => FSM state management logic
}
handleEvent(event: Event): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
// Audio region transitions
if (event === "unmute" && this.audioState === "Muted") {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => Event type check
this.audioState = "Playing"; // => Muted → Playing (audio region only)
} else if (event === "mute" && this.audioState === "Playing") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
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
// => Comparison check
// => Begin object/config definition
this.videoState = "Streaming"; // => Paused → Streaming (video region only)
} else if (event === "pause" && this.videoState === "Streaming") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
this.videoState = "Paused"; // => Streaming → Paused (video region only)
} else {
// => Statement execution
// => 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' }
// => FSM state management logic
// => Pure read, no side effects
player.handleEvent("play"); // => Video region: Paused → Streaming (audio unchanged)
// => FSM state management logic
// => Processes events, triggers transitions
console.log(player.getCurrentState()); // => Output: { audio: 'Muted', video: 'Streaming' }
// => FSM state management logic
// => Pure read, no side effects
player.handleEvent("unmute"); // => Audio region: Muted → Playing (video unchanged)
// => FSM state management logic
// => Processes events, triggers transitions
console.log(player.getCurrentState()); // => Output: { audio: 'Playing', video: 'Streaming' }
// => FSM state management logic
// => Pure read, no side effects
player.handleEvent("pause"); // => Video region: Streaming → Paused (audio unchanged)
// => FSM state management logic
// => Processes events, triggers transitions
console.log(player.getCurrentState()); // => Output: { audio: 'Playing', video: 'Paused' }
// => FSM state management logic
// => Pure read, no side effects
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.
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 {
// => Class encapsulates FSM logic
// => 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 } {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return { power: this.powerState, display: this.displayState }; // => Returns value to caller
// => Access instance property
// => Return computed result
}
handleEvent(event: Event): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (event === "powerOn" && this.powerState === "Off") {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => 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") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
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") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
this.displayState = "Showing"; // => Display: Hidden → Showing (only if powered on)
} else if (event === "hide" && this.powerState === "On" && this.displayState === "Showing") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
this.displayState = "Hidden"; // => Display: Showing → Hidden (only if powered on)
} else {
// => Statement execution
// => 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' }
// => FSM state management logic
// => Pure read, no side effects
device.handleEvent("powerOn"); // => Power On → forces Display to Showing
// => Processes events, triggers transitions
// => Output: Power on: Display forced to Showing
console.log(device.getCurrentState()); // => Output: { power: 'On', display: 'Showing' }
// => FSM state management logic
// => Pure read, no side effects
device.handleEvent("hide"); // => Display: Showing → Hidden (independent)
// => FSM state management logic
// => Processes events, triggers transitions
console.log(device.getCurrentState()); // => Output: { power: 'On', display: 'Hidden' }
// => FSM state management logic
// => Pure read, no side effects
device.handleEvent("powerOff"); // => Power Off → forces Display to Hidden
// => Processes events, triggers transitions
// => Output: Power off: Display forced to Hidden
console.log(device.getCurrentState()); // => Output: { power: 'Off', display: 'Hidden' }
// => FSM state management logic
// => Pure read, no side effects
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.
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 {
// => Class encapsulates FSM logic
// => 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 } {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return { auth: this.authState, data: this.dataState, combined: this.combinedState }; // => Returns value to caller
// => Access instance property
// => Return computed result
}
private checkReadiness(): void {
// => Method invocation
// => 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
// => Comparison check
// => 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 {
// => Statement execution
// => Fallback branch
this.combinedState = "NotReady"; // => Either region not ready → App not ready
}
}
handleEvent(event: Event): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (event === "login" && this.authState === "Unauthenticated") {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => Event type check
this.authState = "Authenticated"; // => Auth: Unauthenticated → Authenticated
this.checkReadiness(); // => Check if join condition met
} else if (event === "loadData" && this.dataState === "NotLoaded") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
this.dataState = "Loaded"; // => Data: NotLoaded → Loaded
this.checkReadiness(); // => Check if join condition met
} else if (event === "logout") {
// => Method signature: defines function interface
// => Comparison check
// => Alternative condition
this.authState = "Unauthenticated"; // => Auth: reset
this.checkReadiness(); // => Combined becomes NotReady
} else if (event === "clearData") {
// => Method signature: defines function interface
// => Comparison check
// => Alternative condition
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
// => Pure read, no side effects
// => Output: { auth: 'Unauthenticated', data: 'NotLoaded', combined: 'NotReady' }
app.handleEvent("login"); // => Auth ready, but data not ready yet
// => Processes events, triggers transitions
console.log(app.getCurrentState()); // => Output for verification
// => Chained method calls or nested operations
// => Query method: read current FSM state
// => Pure read, no side effects
// => Output: { auth: 'Authenticated', data: 'NotLoaded', combined: 'NotReady' }
app.handleEvent("loadData"); // => Data ready → JOIN condition met!
// => Processes events, triggers transitions
// => 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
// => Pure read, no side effects
// => Output: { auth: 'Authenticated', data: 'Loaded', combined: 'Ready' }
app.handleEvent("logout"); // => Auth reset → Join broken
// => Processes events, triggers transitions
console.log(app.getCurrentState()); // => Output for verification
// => Chained method calls or nested operations
// => Query method: read current FSM state
// => Pure read, no side effects
// => 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 {
// => Class encapsulates FSM logic
// => 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 {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
if (this.state === "single") {
// => State-based guard condition
// => Comparison check
// => Guard condition: check current state is single
// => Only execute if condition true
return { mode: "single", state: this.singleState }; // => Single state active
// => FSM state management logic
} else {
// => Statement execution
// => Fallback branch
return { mode: "forked", workerA: this.workerA, workerB: this.workerB }; // => Both regions active
// => FSM state management logic
}
}
handleEvent(event: Event): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (event === "start" && this.state === "single" && this.singleState === "Idle") {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => 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") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
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") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
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 {
// => Method invocation
// => Extended state (data beyond FSM state)
if (this.workerA === "DoneA" && this.workerB === "DoneB") {
// => Conditional branch
// => Logical AND: both conditions must be true
// => Comparison check
// => 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' }
// => FSM state management logic
// => Pure read, no side effects
processor.handleEvent("start"); // => Fork: Idle → ProcessingA + ProcessingB
// => Processes events, triggers transitions
// => 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
// => Pure read, no side effects
// => Output: { mode: 'forked', workerA: 'ProcessingA', workerB: 'ProcessingB' }
processor.handleEvent("completeA"); // => Worker A done (B still processing)
// => FSM state management logic
// => Processes events, triggers transitions
// => Output: Worker A completed
console.log(processor.getCurrentState()); // => Output for verification
// => Chained method calls or nested operations
// => Query method: read current FSM state
// => Pure read, no side effects
// => Output: { mode: 'forked', workerA: 'DoneA', workerB: 'ProcessingB' }
processor.handleEvent("completeB"); // => Worker B done → JOIN back to Idle
// => Processes events, triggers transitions
// => 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
// => Pure read, no side effects
// => 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.
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 {
// => Class encapsulates FSM logic
// => 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 } {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return { connectivity: this.connectivity, auth: this.auth }; // => Returns value to caller
// => Access instance property
// => Return computed result
}
handleEvent(event: Event): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
// Connectivity region
if (event === "disconnect" && this.connectivity === "Online") {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => Event type check
this.connectivity = "Offline"; // => Online → Offline
} else if (event === "reconnect" && this.connectivity === "Offline") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
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
// => Comparison check
// => Begin object/config definition
this.auth = "LoggedIn"; // => LoggedOut → LoggedIn
// => Can login while Offline (credential caching)
} else if (event === "logout" && this.auth === "LoggedIn") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
this.auth = "LoggedOut"; // => LoggedIn → LoggedOut
}
}
}
// Usage
const system = new System(); // => connectivity: Online, auth: LoggedOut
console.log(system.getCurrentState()); // => Output: { connectivity: 'Online', auth: 'LoggedOut' }
// => FSM state management logic
// => Pure read, no side effects
system.handleEvent("login"); // => Auth: LoggedOut → LoggedIn (connectivity unchanged)
// => FSM state management logic
// => Processes events, triggers transitions
console.log(system.getCurrentState()); // => Output: { connectivity: 'Online', auth: 'LoggedIn' }
// => FSM state management logic
// => Pure read, no side effects
system.handleEvent("disconnect"); // => Connectivity: Online → Offline (auth unchanged)
// => FSM state management logic
// => Processes events, triggers transitions
console.log(system.getCurrentState()); // => Output: { connectivity: 'Offline', auth: 'LoggedIn' }
// => FSM state management logic
// => Pure read, no side effects
// => 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.
Example 42: Broadcast Events to Parallel Regions
Single events can trigger transitions in multiple parallel regions simultaneously.
TypeScript Implementation:
// 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 {
// => Class encapsulates FSM logic
// => 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 } {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return { region1: this.region1, region2: this.region2 }; // => Returns value to caller
// => Access instance property
// => Return computed result
}
handleEvent(event: Event): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (event === "activate") {
// => Event type guard condition
// => Comparison check
// => Event type check
// Broadcast: activates BOTH regions
if (this.region1 === "R1_Idle") {
// => Conditional branch
// => Comparison check
// => Conditional check
// => Branch execution based on condition
this.region1 = "R1_Active"; // => Region 1: Idle → Active
}
if (this.region2 === "R2_Idle") {
// => Conditional branch
// => Comparison check
// => 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") {
// => Method signature: defines function interface
// => Comparison check
// => Alternative condition
// Broadcast: deactivates BOTH regions
if (this.region1 === "R1_Active") {
// => Conditional branch
// => Comparison check
// => Conditional check
// => Branch execution based on condition
this.region1 = "R1_Idle"; // => Region 1: Active → Idle
}
if (this.region2 === "R2_Active") {
// => Conditional branch
// => Comparison check
// => 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
// => Comparison check
// => Alternative condition
// 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
// => Pure read, no side effects
// => Output: { region1: 'R1_Idle', region2: 'R2_Idle' }
multiSystem.handleEvent("activate"); // => Broadcast: both regions activate
// => Processes events, triggers transitions
// => Output: Broadcast: activated both regions
console.log(multiSystem.getCurrentState()); // => Output for verification
// => Chained method calls or nested operations
// => Query method: read current FSM state
// => Pure read, no side effects
// => Output: { region1: 'R1_Active', region2: 'R2_Active' }
multiSystem.handleEvent("reset"); // => Broadcast: reset all regions
// => Processes events, triggers transitions
// => Output: Broadcast: reset all regions
console.log(multiSystem.getCurrentState()); // => Output for verification
// => Chained method calls or nested operations
// => Query method: read current FSM state
// => Pure read, no side effects
// => 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.
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
// => Assign value
// => Defines event alphabet for FSM
// => Events trigger state transitions
class ConfigurableSystem {
// => Class encapsulates FSM logic
// => 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 } {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return { featureA: this.featureA, featureB: this.featureB }; // => Returns value to caller
// => Access instance property
// => Return computed result
}
handleEvent(event: Event): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (event === "enableFeatureA") {
// => Event type guard condition
// => Comparison check
// => Event type check
if (this.featureA === null) {
// => Conditional branch
// => Comparison check
// => 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
// => Comparison check
// => Conditional check
// => Branch execution based on condition
this.featureA = "Enabled"; // => Disabled → Enabled
}
} else if (event === "disableFeatureA") {
// => Method signature: defines function interface
// => Comparison check
// => Alternative condition
if (this.featureA === "Enabled") {
// => Conditional branch
// => Comparison check
// => Conditional check
// => Branch execution based on condition
this.featureA = "Disabled"; // => Enabled → Disabled
}
} else if (event === "enableFeatureB") {
// => Method signature: defines function interface
// => Comparison check
// => Alternative condition
if (this.featureB === null) {
// => Conditional branch
// => Comparison check
// => 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
// => Comparison check
// => Conditional check
// => Branch execution based on condition
this.featureB = "Enabled"; // => Statement execution
// => Access instance property
}
} else if (event === "disableFeatureB") {
// => Method signature: defines function interface
// => Comparison check
// => Alternative condition
if (this.featureB === "Enabled") {
// => Conditional branch
// => Comparison check
// => Conditional check
// => Branch execution based on condition
this.featureB = "Disabled"; // => Statement execution
// => Access instance property
}
}
}
}
// Usage
const configSystem = new ConfigurableSystem(); // => both regions: null (inactive)
// => FSM state management logic
console.log(configSystem.getCurrentState()); // => Output for verification
// => Chained method calls or nested operations
// => Query method: read current FSM state
// => Pure read, no side effects
// => Output: { featureA: null, featureB: null }
configSystem.handleEvent("enableFeatureA"); // => Activate region A
// => Processes events, triggers transitions
// => Output: Feature A region activated
console.log(configSystem.getCurrentState()); // => Output for verification
// => Chained method calls or nested operations
// => Query method: read current FSM state
// => Pure read, no side effects
// => Output: { featureA: 'Enabled', featureB: null }
configSystem.handleEvent("enableFeatureB"); // => Activate region B
// => Processes events, triggers transitions
// => Output: Feature B region activated
console.log(configSystem.getCurrentState()); // => Output for verification
// => Chained method calls or nested operations
// => Query method: read current FSM state
// => Pure read, no side effects
// => 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.
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 {
// => Class encapsulates FSM logic
// => 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 } {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return { worker: this.worker, monitor: this.monitor }; // => Returns value to caller
// => Access instance property
// => Return computed result
}
handleEvent(event: Event): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (event === "work" && this.worker === "Stopped") {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => Event type check
this.worker = "Working"; // => Worker starts
} else if (event === "error" && this.worker === "Working") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
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") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
this.monitor = "Monitoring"; // => Monitor acknowledges alert
// => Worker region unchanged (error persists)
} else if (event === "stop") {
// => Method signature: defines function interface
// => Comparison check
// => Alternative condition
this.worker = "Stopped"; // => Worker stops
if (this.monitor === "AlertSent") {
// => Conditional branch
// => Comparison check
// => 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
// => Pure read, no side effects
// => Output: { worker: 'Stopped', monitor: 'Monitoring' }
resilientSys.handleEvent("work"); // => Worker: Stopped → Working
// => Processes events, triggers transitions
console.log(resilientSys.getCurrentState()); // => Output for verification
// => Chained method calls or nested operations
// => Query method: read current FSM state
// => Pure read, no side effects
// => Output: { worker: 'Working', monitor: 'Monitoring' }
resilientSys.handleEvent("error"); // => Error propagates across regions
// => Processes events, triggers transitions
// => Output: Error propagated: Worker → Monitor
console.log(resilientSys.getCurrentState()); // => Output for verification
// => Chained method calls or nested operations
// => Query method: read current FSM state
// => Pure read, no side effects
// => Output: { worker: 'Error', monitor: 'AlertSent' }
resilientSys.handleEvent("acknowledge"); // => Monitor clears, worker error persists
// => Processes events, triggers transitions
console.log(resilientSys.getCurrentState()); // => Output for verification
// => Chained method calls or nested operations
// => Query method: read current FSM state
// => Pure read, no side effects
// => Output: { worker: 'Error', monitor: 'Monitoring' }
resilientSys.handleEvent("stop"); // => Worker stops, monitor clears
// => Processes events, triggers transitions
// => 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
// => Pure read, no side effects
// => 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 {
// => Class encapsulates FSM logic
// => 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 {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns value to caller
// => Access instance property
// => Return current state value
}
private saveHistory(): void {
// => Method invocation
// => 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 method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (event === "powerOn" && this.state === "Off") {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => Event type check
// => Combined (state, event) guard
this.state = "On.Low"; // => Default: enter at Low
this.saveHistory(); // => Method invocation
} else if (event === "powerOff" && this.state.startsWith("On.")) {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
this.saveHistory(); // => Save before exiting
this.state = "Off"; // => Exit to Off
} else if (event === "resume" && this.state === "Off" && this.history) {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
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") {
// => Method signature: defines function interface
// => Comparison check
// => Alternative condition
if (this.state === "On.Low")
this.state = "On.Medium"; // => State-based guard condition
// => Comparison check
// => Conditional check
// => Branch execution based on condition
else if (this.state === "On.Medium") this.state = "On.High"; // => Alternative conditional branch
// => Comparison check
this.saveHistory(); // => Method invocation
} else if (event === "decrease") {
// => Method signature: defines function interface
// => Comparison check
// => Alternative condition
if (this.state === "On.High")
this.state = "On.Medium"; // => State-based guard condition
// => Comparison check
// => Conditional check
// => Branch execution based on condition
else if (this.state === "On.Medium") this.state = "On.Low"; // => Alternative conditional branch
// => Comparison check
this.saveHistory(); // => Method invocation
}
}
}
// Usage
const brightness = new BrightnessControl(); // => state: Off, history: null
brightness.handleEvent("powerOn"); // => Off → On.Low
// => Processes events, triggers transitions
// => Output: History saved: On.Low
console.log(brightness.getCurrentState()); // => Output: On.Low
// => FSM state management logic
// => Pure read, no side effects
brightness.handleEvent("increase"); // => On.Low → On.Medium
// => Processes events, triggers transitions
// => Output: History saved: On.Medium
console.log(brightness.getCurrentState()); // => Output: On.Medium
// => FSM state management logic
// => Pure read, no side effects
brightness.handleEvent("increase"); // => On.Medium → On.High
// => Processes events, triggers transitions
// => Output: History saved: On.High
console.log(brightness.getCurrentState()); // => Output: On.High
// => FSM state management logic
// => Pure read, no side effects
brightness.handleEvent("powerOff"); // => On.High → Off (save High in history)
// => FSM state management logic
// => Processes events, triggers transitions
// => Output: History saved: On.High
console.log(brightness.getCurrentState()); // => Output: Off
// => FSM state management logic
// => Pure read, no side effects
brightness.handleEvent("resume"); // => Off → On.High (restore from history)
// => FSM state management logic
// => Processes events, triggers transitions
// => Output: Resumed from history: On.High
console.log(brightness.getCurrentState()); // => Output: On.High
// => FSM state management logic
// => Pure read, no side effects
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.
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 {
// => Class encapsulates FSM logic
// => 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 {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns value to caller
// => Access instance property
// => Return current state value
}
private saveDeepHistory(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
if (this.state !== "Off") {
// => State-based guard condition
// => Comparison check
// => 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 method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (event === "powerOn" && this.state === "Off") {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => Event type check
// => Combined (state, event) guard
this.state = "On.Playing.Song1"; // => Default: enter at Song1
this.saveDeepHistory(); // => Method invocation
} else if (event === "powerOff" && this.state !== "Off") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
this.saveDeepHistory(); // => Save before exiting
this.state = "Off"; // => State transition execution
// => Access instance property
// => Transition: set state to Off
// => State mutation (core FSM operation)
} else if (event === "resume" && this.state === "Off" && this.deepHistory) {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
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")) {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
this.saveDeepHistory(); // => Method invocation
this.state = "On.Paused"; // => Playing → Paused
} else if (event === "play" && this.state === "On.Paused" && this.deepHistory?.startsWith("On.Playing")) {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
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") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
this.state = "On.Playing.Song2"; // => State transition execution
// => Access instance property
this.saveDeepHistory(); // => Method invocation
}
}
}
// Usage
const player = new MusicPlayer(); // => state: Off
player.handleEvent("powerOn"); // => Off → On.Playing.Song1
// => Processes events, triggers transitions
// => Output: Deep history saved: On.Playing.Song1
console.log(player.getCurrentState()); // => Output: On.Playing.Song1
// => FSM state management logic
// => Pure read, no side effects
player.handleEvent("nextSong"); // => Song1 → Song2
// => Processes events, triggers transitions
// => Output: Deep history saved: On.Playing.Song2
console.log(player.getCurrentState()); // => Output: On.Playing.Song2
// => FSM state management logic
// => Pure read, no side effects
player.handleEvent("pause"); // => Playing.Song2 → Paused
// => Processes events, triggers transitions
// => Output: Deep history saved: On.Playing.Song2
console.log(player.getCurrentState()); // => Output: On.Paused
// => FSM state management logic
// => Pure read, no side effects
player.handleEvent("play"); // => Paused → restore exact playing state (Song2)
// => FSM state management logic
// => Processes events, triggers transitions
// => Output: Resume play: On.Playing.Song2
console.log(player.getCurrentState()); // => Output: On.Playing.Song2
// => FSM state management logic
// => Pure read, no side effects
player.handleEvent("powerOff"); // => Save and power off
// => Processes events, triggers transitions
// => Output: Deep history saved: On.Playing.Song2
player.handleEvent("resume"); // => Resume full state path
// => Processes events, triggers transitions
// => Output: Deep resume: On.Playing.Song2
console.log(player.getCurrentState()); // => Output: On.Playing.Song2
// => FSM state management logic
// => Pure read, no side effects
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 {
// => Class encapsulates FSM logic
// => 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 {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns value to caller
// => Access instance property
// => Return current state value
}
private saveHistory(): void {
// => Method invocation
// => 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 {
// => Method invocation
// => 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 method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (event === "start" && this.state === "Idle") {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => Event type check
// => Combined (state, event) guard
this.state = "Working"; // => State transition execution
// => Access instance property
// => Transition: set state to Working
// => State mutation (core FSM operation)
} else if (event === "pause" && this.state === "Working") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
this.saveHistory(); // => Save Working state
this.state = "Paused"; // => State transition execution
// => Access instance property
// => Transition: set state to Paused
// => State mutation (core FSM operation)
} else if (event === "resume" && this.state === "Paused") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
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 {
// => Statement execution
// => 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; // => Statement execution
// => Access instance property
}
}
}
// Usage
const timedFSM = new TimedHistory(); // => state: Idle
timedFSM.handleEvent("start"); // => Idle → Working
// => Processes events, triggers transitions
console.log(timedFSM.getCurrentState()); // => Output: Working
// => FSM state management logic
// => Pure read, no side effects
timedFSM.handleEvent("pause"); // => Working → Paused (save history)
// => FSM state management logic
// => Processes events, triggers transitions
// => Output: History saved: Working at [timestamp]
console.log(timedFSM.getCurrentState()); // => Output: Paused
// => FSM state management logic
// => Pure read, no side effects
// Resume immediately (within timeout)
timedFSM.handleEvent("resume"); // => Paused → Working (history valid)
// => FSM state management logic
// => Processes events, triggers transitions
// => Output: Resumed from history: Working
console.log(timedFSM.getCurrentState()); // => Output: Working
// => FSM state management logic
// => Pure read, no side effects
// Pause again and wait for timeout
timedFSM.handleEvent("pause"); // => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
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
// => Begin object/config definition
state: State; // => Statement execution
timestamp: number; // => Statement execution
networkQuality: "good" | "poor"; // => Additional context
}
class ConditionalHistory {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: State = "Disconnected"; // => State field: stores current FSM state privately
// => Assign value
// => Mutable state storage (single source of truth)
// => FSM begins execution in Disconnected state
private history: HistoryContext | null = null; // => Field declaration: class member variable
// => Assign value
// => Extended state (data beyond FSM state)
// => Initialized alongside FSM state
getCurrentState(): State {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns value to caller
// => Access instance property
// => Return current state value
}
private saveHistory(networkQuality: "good" | "poor"): void {
// => Method invocation
// => Extended state (data beyond FSM state)
this.history = {
// => Statement execution
// => Access instance property
// => Begin object/config definition
state: this.state, // => Statement execution
// => Access instance property
timestamp: Date.now(), // => Method invocation
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 {
// => Method invocation
// => 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
// => Assign value
// => 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
// => Comparison check
// => 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 method
// => Assign value
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (event === "connect" && this.state === "Disconnected") {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => Event type check
// => Combined (state, event) guard
this.state = "Connected.Idle"; // => State transition execution
// => Access instance property
} else if (event === "sync" && this.state === "Connected.Idle") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
this.state = "Connected.Syncing"; // => State transition execution
// => Access instance property
this.saveHistory(networkQuality); // => Method invocation
} else if (event === "disconnect" && this.state.startsWith("Connected.")) {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
this.saveHistory(networkQuality); // => Save before disconnect
this.state = "Disconnected"; // => State transition execution
// => Access instance property
// => Transition: set state to Disconnected
// => State mutation (core FSM operation)
} else if (event === "reconnect" && this.state === "Disconnected") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
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 {
// => Statement execution
// => 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
// => Processes events, triggers transitions
condFSM.handleEvent("sync", "good"); // => Idle → Syncing (good network)
// => FSM state management logic
// => Processes events, triggers transitions
// => Output: History saved: Connected.Syncing (network: good)
condFSM.handleEvent("disconnect", "good"); // => Syncing → Disconnected
// => Processes events, triggers transitions
// => Output: History saved: Connected.Syncing (network: good)
condFSM.handleEvent("reconnect", "good"); // => Restore (validation passes)
// => FSM state management logic
// => Processes events, triggers transitions
// => Output: History validation passed
// => Restored: Connected.Syncing
console.log(condFSM.getCurrentState()); // => Output: Connected.Syncing
// => FSM state management logic
// => Pure read, no side effects
// Usage - Failed restoration (network degraded)
const condFSM2 = new ConditionalHistory(); // => Instance creation via constructor
// => Create new instance
// => Initialize condFSM2
condFSM2.handleEvent("connect"); // => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
condFSM2.handleEvent("sync", "good"); // => Save with good network
// => Processes events, triggers transitions
condFSM2.handleEvent("disconnect", "good"); // => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
condFSM2.handleEvent("reconnect", "poor"); // => Reconnect with poor network
// => Processes events, triggers transitions
// => Output: History validation failed: network degraded
// => Fallback to default: Connected.Idle
console.log(condFSM2.getCurrentState()); // => Output: Connected.Idle
// => FSM state management logic
// => Pure read, no side effects
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
// => Begin object/config definition
handle(context: TrafficLight): void; // => Each state handles its own transitions
// => Processes events, triggers transitions
toString(): string; // => State name for logging
}
class RedState implements State {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
handle(context: TrafficLight): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
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 {
// => Method invocation
// => Begin object/config definition
return "Red"; // => State name
}
}
class GreenState implements State {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
handle(context: TrafficLight): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
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 {
// => Method invocation
// => Begin object/config definition
return "Green"; // => Returns value to caller
// => Return computed result
}
}
class YellowState implements State {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
handle(context: TrafficLight): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
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 {
// => Method invocation
// => Begin object/config definition
return "Yellow"; // => Returns value to caller
// => Return computed result
}
}
class TrafficLight {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: State; // => Current state object
constructor() {
// => Method invocation
// => Begin object/config definition
this.state = new RedState(); // => Initial state: Red
// => Traffic light state management
}
setState(state: State): void {
// => Accessor method definition
// => Begin object/config definition
this.state = state; // => Transition to new state
// => FSM state management logic
console.log(`State changed to: ${state.toString()}`); // => Output for verification
// => Chained method calls or nested operations
// => Debug/audit output
// => Log for observability
}
next(): void {
// => Method invocation
// => Begin object/config definition
this.state.handle(this); // => Delegate to current state
// => Processes events, triggers transitions
}
getCurrentState(): string {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state.toString(); // => State name
}
}
// Usage
const traffic = new TrafficLight(); // => state: Red
console.log(`Current: ${traffic.getCurrentState()}`); // => Output: Current: Red
// => FSM state management logic
// => Pure read, no side effects
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
// => FSM state management logic
// => Pure read, no side effects
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
// => FSM state management logic
// => Pure read, no side effects
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
// => Begin object/config definition
onEnter(context: Application): void; // => Called when entering state
onExit(context: Application): void; // => Called when exiting state
handle(event: string, context: Application): void; // => Handle events
// => Processes events, triggers transitions
getName(): string; // => Accessor method definition
}
class LoadingState implements StateWithLifecycle {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
onEnter(context: Application): void {
// => Method invocation
// => Begin object/config definition
console.log("Loading: Initialize resources"); // => Entry action
// => Log for observability
}
onExit(context: Application): void {
// => Method invocation
// => Begin object/config definition
console.log("Loading: Cleanup loaders"); // => Exit action
// => Log for observability
}
handle(event: string, context: Application): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (event === "loaded") {
// => Event type guard condition
// => Comparison check
// => Event type check
context.transitionTo(new ReadyState()); // => Loading → Ready
// => FSM state management logic
}
}
getName(): string {
// => Accessor method definition
// => Begin object/config definition
return "Loading"; // => Returns value to caller
// => Return computed result
}
}
class ReadyState implements StateWithLifecycle {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
onEnter(context: Application): void {
// => Method invocation
// => Begin object/config definition
console.log("Ready: Application ready for use"); // => Output for verification
// => Debug/audit output
// => Log for observability
}
onExit(context: Application): void {
// => Method invocation
// => Begin object/config definition
console.log("Ready: Pausing operations"); // => Output for verification
// => Debug/audit output
// => Log for observability
}
handle(event: string, context: Application): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (event === "error") {
// => Event type guard condition
// => Comparison check
// => Event type check
context.transitionTo(new ErrorState()); // => Ready → Error
// => FSM state management logic
}
}
getName(): string {
// => Accessor method definition
// => Begin object/config definition
return "Ready"; // => Returns value to caller
// => Return computed result
}
}
class ErrorState implements StateWithLifecycle {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
onEnter(context: Application): void {
// => Method invocation
// => Begin object/config definition
console.log("Error: Logging error, notifying user"); // => Output for verification
// => Debug/audit output
// => Log for observability
}
onExit(context: Application): void {
// => Method invocation
// => Begin object/config definition
console.log("Error: Clearing error state"); // => Output for verification
// => Debug/audit output
// => Log for observability
}
handle(event: string, context: Application): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (event === "retry") {
// => Event type guard condition
// => Comparison check
// => Event type check
context.transitionTo(new LoadingState()); // => Error → Loading (retry)
// => FSM state management logic
}
}
getName(): string {
// => Accessor method definition
// => Begin object/config definition
return "Error"; // => Returns value to caller
// => Return computed result
}
}
class Application {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: StateWithLifecycle; // => State field: stores current FSM state privately
// => State variable declaration
constructor() {
// => Method invocation
// => Begin object/config definition
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 {
// => Method invocation
// => Begin object/config definition
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
// => FSM state management logic
}
handleEvent(event: string): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
this.state.handle(event, this); // => Delegate to current state
// => Processes events, triggers transitions
}
getCurrentState(): string {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state.getName(); // => Returns value to caller
// => Return current state value
}
}
// Usage
const app = new Application(); // => state: Loading (onEnter executes)
// => FSM state management logic
// => Output: Loading: Initialize resources
console.log(`Current: ${app.getCurrentState()}`); // => Output: Current: Loading
// => FSM state management logic
// => Pure read, no side effects
app.handleEvent("loaded"); // => Loading → Ready
// => Processes events, triggers transitions
// => Output: Transitioning: Loading → Ready
// => Loading: Cleanup loaders
// => Ready: Application ready for use
console.log(`Current: ${app.getCurrentState()}`); // => Output: Current: Ready
// => FSM state management logic
// => Pure read, no side effects
app.handleEvent("error"); // => Ready → Error
// => Processes events, triggers transitions
// => Output: Transitioning: Ready → Error
// => Ready: Pausing operations
// => Error: Logging error, notifying user
console.log(`Current: ${app.getCurrentState()}`); // => Output: Current: Error
// => FSM state management logic
// => Pure read, no side effects
app.handleEvent("retry"); // => Error → Loading
// => Processes events, triggers transitions
// => 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.
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
// => Begin object/config definition
canTransition(event: string, context: VendingMachine): boolean; // => Guard condition
// => Returns boolean without changing state
handle(event: string, context: VendingMachine): void; // => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
getName(): string; // => Accessor method definition
}
class IdleState implements GuardedState {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
canTransition(event: string, context: VendingMachine): boolean {
// => Method invocation
// => Guard: validates transition possibility
// => Returns boolean without changing state
if (event === "insertCoin") {
// => Event type guard condition
// => Comparison check
// => 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 method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (event === "insertCoin" && this.canTransition(event, context)) {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => 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
// => Comparison check
// => Conditional check
// => Branch execution based on condition
// => Guard for state transition
context.transitionTo(new ReadyState()); // => Idle → Ready (enough money)
// => FSM state management logic
}
} else if (!this.canTransition(event, context)) {
// => Method signature: defines function interface
// => 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 {
// => Accessor method definition
// => Begin object/config definition
return "Idle"; // => Returns value to caller
// => Return computed result
}
}
class ReadyState implements GuardedState {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
canTransition(event: string, context: VendingMachine): boolean {
// => Method invocation
// => Guard: validates transition possibility
// => Returns boolean without changing state
if (event === "selectItem") {
// => Event type guard condition
// => Comparison check
// => 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 method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (event === "selectItem" && this.canTransition(event, context)) {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => Guard: validates transition possibility
// => Returns boolean without changing state
context.transitionTo(new DispensingState()); // => Ready → Dispensing
// => FSM state management logic
} else if (event === "cancel") {
// => Method signature: defines function interface
// => Comparison check
// => Alternative condition
console.log(`Refunding $${context.getBalance() / 100}`); // => Output for verification
// => Chained method calls or nested operations
// => Debug/audit output
// => Log for observability
context.resetBalance(); // => Method invocation
context.transitionTo(new IdleState()); // => Ready → Idle (cancel)
// => Workflow state progression
} else {
// => Statement execution
// => Fallback branch
console.log("Guard failed: Insufficient balance"); // => Output for verification
// => Debug/audit output
// => Log for observability
}
}
getName(): string {
// => Accessor method definition
// => Begin object/config definition
return "Ready"; // => Returns value to caller
// => Return computed result
}
}
class DispensingState implements GuardedState {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
canTransition(event: string, context: VendingMachine): boolean {
// => Method invocation
// => Guard: validates transition possibility
// => Returns boolean without changing state
return event === "dispensed"; // => Only transition on dispensed
}
handle(event: string, context: VendingMachine): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
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 {
// => Accessor method definition
// => Begin object/config definition
return "Dispensing"; // => Returns value to caller
// => Return computed result
}
}
class VendingMachine {
// => Class encapsulates FSM logic
// => 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() {
// => Method invocation
// => Begin object/config definition
this.state = new IdleState(); // => State transition execution
// => Constructor creates new object instance
// => Create new instance
}
getBalance(): number {
// => Accessor method definition
// => Begin object/config definition
return this.balance; // => Returns value to caller
// => Access instance property
// => Return computed result
}
addBalance(amount: number): void {
// => Method invocation
// => Begin object/config definition
this.balance += amount; // => Statement execution
// => Modify state data
// => Update extended state data
}
deductItemCost(cost: number): void {
// => Method invocation
// => Begin object/config definition
this.balance -= cost; // => Statement execution
// => Modify state data
// => Update extended state data
}
resetBalance(): void {
// => Method invocation
// => Begin object/config definition
this.balance = 0; // => Statement execution
// => Access instance property
}
transitionTo(newState: GuardedState): void {
// => Method invocation
// => Begin object/config definition
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
// => Access instance property
}
handleEvent(event: string): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
this.state.handle(event, this); // => Delegate to state
// => Processes events, triggers transitions
}
getCurrentState(): string {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
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
// => FSM state management logic
// => Pure read, no side effects
vending.handleEvent("insertCoin"); // => Add $0.25 (balance: $0.25)
// => FSM state management logic
// => Processes events, triggers transitions
// => Output: Coin inserted. Balance: $0.25
console.log(`Balance: $${vending.getBalance() / 100}`); // => Output: Balance: $0.25
// => FSM state management logic
vending.handleEvent("insertCoin"); // => Add $0.25 (balance: $0.50) → Ready
// => FSM state management logic
// => Processes events, triggers transitions
// => Output: Coin inserted. Balance: $0.5
// => Transition: Idle → Ready
console.log(`Current: ${vending.getCurrentState()}`); // => Output: Current: Ready
// => FSM state management logic
// => Pure read, no side effects
vending.handleEvent("selectItem"); // => Ready → Dispensing (guard passes)
// => FSM state management logic
// => Processes events, triggers transitions
// => Output: Transition: Ready → Dispensing
// => Dispensing item...
// => Transition: Dispensing → Idle
console.log(`Current: ${vending.getCurrentState()}`); // => Output: Current: Idle
// => FSM state management logic
// => Pure read, no side effects
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
// => Begin object/config definition
handle(event: string, context: ConnectionContext): void; // => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
getName(): string; // => Accessor method definition
}
class DisconnectedState implements ConnectionState {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
handle(event: string, context: ConnectionContext): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (event === "connect") {
// => Event type guard condition
// => Comparison check
// => 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
// => FSM state management logic
} else {
// => Statement execution
// => Fallback branch
context.transitionTo(new ConnectingState()); // => Disconnected → Connecting
// => FSM state management logic
}
}
}
getName(): string {
// => Accessor method definition
// => Begin object/config definition
return "Disconnected"; // => Returns value to caller
// => Return computed result
}
}
class ConnectingState implements ConnectionState {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
handle(event: string, context: ConnectionContext): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (event === "success") {
// => Event type guard condition
// => Comparison check
// => Event type check
context.resetAttempts(); // => Reset attempt counter
context.transitionTo(new ConnectedState()); // => Connecting → Connected
// => FSM state management logic
} else if (event === "failure") {
// => Method signature: defines function interface
// => Comparison check
// => Alternative condition
context.transitionTo(new DisconnectedState()); // => Retry
// => FSM state management logic
}
}
getName(): string {
// => Accessor method definition
// => Begin object/config definition
return "Connecting"; // => Returns value to caller
// => Return computed result
}
}
class ConnectedState implements ConnectionState {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
handle(event: string, context: ConnectionContext): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (event === "disconnect") {
// => Event type guard condition
// => Comparison check
// => Event type check
context.transitionTo(new DisconnectedState()); // => Connected → Disconnected
// => FSM state management logic
}
}
getName(): string {
// => Accessor method definition
// => Begin object/config definition
return "Connected"; // => Returns value to caller
// => Return computed result
}
}
class BackoffState implements ConnectionState {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
handle(event: string, context: ConnectionContext): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (event === "retry") {
// => Event type guard condition
// => Comparison check
// => 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
// => FSM state management logic
}
}
getName(): string {
// => Accessor method definition
// => Begin object/config definition
return "Backoff"; // => Returns value to caller
// => Return computed result
}
}
class ConnectionContext {
// => Class encapsulates FSM logic
// => 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() {
// => Method invocation
// => Begin object/config definition
this.state = new DisconnectedState(); // => State transition execution
// => Constructor creates new object instance
// => Create new instance
}
getAttempts(): number {
// => Accessor method definition
// => Begin object/config definition
return this.attempts; // => Read context data
}
incrementAttempts(): void {
// => Method invocation
// => Begin object/config definition
this.attempts++; // => Modify context data
}
resetAttempts(): void {
// => Method invocation
// => Begin object/config definition
this.attempts = 0; // => Statement execution
// => Access instance property
}
transitionTo(newState: ConnectionState): void {
// => Method invocation
// => Begin object/config definition
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
// => Access instance property
}
handleEvent(event: string): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
this.state.handle(event, this); // => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
}
getCurrentState(): string {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
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
// => Processes events, triggers transitions
// => Output: Connection attempt 1
// => Transition: Disconnected → Connecting
conn.handleEvent("failure"); // => Connecting → Disconnected (retry)
// => FSM state management logic
// => Processes events, triggers transitions
// => Output: Transition: Connecting → Disconnected
conn.handleEvent("connect"); // => Attempt 2
// => Processes events, triggers transitions
// => Output: Connection attempt 2
// => Transition: Disconnected → Connecting
conn.handleEvent("failure"); // => Retry again
// => Processes events, triggers transitions
conn.handleEvent("connect"); // => Attempt 3
// => Processes events, triggers transitions
conn.handleEvent("failure"); // => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
conn.handleEvent("connect"); // => Attempt 4: Too many → Backoff
// => Processes events, triggers transitions
// => Output: Connection attempt 4
// => Too many attempts. Backing off...
// => Transition: Disconnected → Backoff
conn.handleEvent("retry"); // => Backoff → Disconnected (reset)
// => FSM state management logic
// => Processes events, triggers transitions
// => Output: Backoff complete. Retrying...
// => Transition: Backoff → Disconnected
console.log(`Attempts: ${conn.getAttempts()}`); // => Output: Attempts: 0
// => FSM state management logic
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
// => Begin object/config definition
pay(amount: number): void; // => Strategy: how to pay
}
class CreditCardStrategy implements PaymentStrategy {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
pay(amount: number): void {
// => Method invocation
// => Begin object/config definition
console.log(`Paid $${amount} via Credit Card`); // => Output for verification
// => Debug/audit output
// => Log for observability
}
}
class PayPalStrategy implements PaymentStrategy {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
pay(amount: number): void {
// => Method invocation
// => Begin object/config definition
console.log(`Paid $${amount} via PayPal`); // => Output for verification
// => Debug/audit output
// => Log for observability
}
}
interface CheckoutState {
// => Type declaration defines structure
// => Begin object/config definition
handle(event: string, context: CheckoutProcess): void; // => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
getName(): string; // => Accessor method definition
}
class SelectingPaymentState implements CheckoutState {
// => Class encapsulates FSM logic
// => 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 method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (event === "selectCreditCard") {
// => Event type guard condition
// => Comparison check
// => Event type check
this.strategy = new CreditCardStrategy(); // => Choose strategy
// => FSM state management logic
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()); // => Method invocation
// => Constructor creates new object instance
} else if (event === "selectPayPal") {
// => Method signature: defines function interface
// => Comparison check
// => Alternative condition
this.strategy = new PayPalStrategy(); // => Method invocation
// => 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); // => Method invocation
context.transitionTo(new ProcessingPaymentState()); // => Method invocation
// => Constructor creates new object instance
}
}
getName(): string {
// => Accessor method definition
// => Begin object/config definition
return "SelectingPayment"; // => Returns value to caller
// => Return computed result
}
}
class ProcessingPaymentState implements CheckoutState {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
handle(event: string, context: CheckoutProcess): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (event === "confirm") {
// => Event type guard condition
// => Comparison check
// => 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
// => FSM state management logic
context.transitionTo(new CompletedState()); // => Method invocation
// => Constructor creates new object instance
}
} else if (event === "cancel") {
// => Method signature: defines function interface
// => Comparison check
// => Alternative condition
context.transitionTo(new SelectingPaymentState()); // => Back to selection
// => FSM state management logic
}
}
getName(): string {
// => Accessor method definition
// => Begin object/config definition
return "ProcessingPayment"; // => Returns value to caller
// => Return computed result
}
}
class CompletedState implements CheckoutState {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
handle(event: string, context: CheckoutProcess): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
console.log("Checkout complete. No further actions."); // => Output for verification
// => Debug/audit output
// => Log for observability
}
getName(): string {
// => Accessor method definition
// => Begin object/config definition
return "Completed"; // => Returns value to caller
// => Return computed result
}
}
class CheckoutProcess {
// => Class encapsulates FSM logic
// => 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) {
// => Method invocation
// => Begin object/config definition
this.amount = amount; // => Statement execution
// => Access instance property
this.state = new SelectingPaymentState(); // => State transition execution
// => Constructor creates new object instance
// => Create new instance
}
getAmount(): number {
// => Accessor method definition
// => Begin object/config definition
return this.amount; // => Returns value to caller
// => Access instance property
// => Return computed result
}
setPaymentStrategy(strategy: PaymentStrategy): void {
// => Accessor method definition
// => Begin object/config definition
this.paymentStrategy = strategy; // => Statement execution
// => Access instance property
}
getPaymentStrategy(): PaymentStrategy | null {
// => Accessor method definition
// => Begin object/config definition
return this.paymentStrategy; // => Returns value to caller
// => Access instance property
// => Return computed result
}
transitionTo(newState: CheckoutState): void {
// => Method invocation
// => Begin object/config definition
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
// => Access instance property
}
handleEvent(event: string): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
this.state.handle(event, this); // => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
}
getCurrentState(): string {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
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
// => Processes events, triggers transitions
// => Output: Payment method: Credit Card
// => Transition: SelectingPayment → ProcessingPayment
checkout.handleEvent("confirm"); // => Execute payment via Credit Card strategy
// => Processes events, triggers transitions
// => 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
// => Processes events, triggers transitions
// => Output: Payment method: PayPal
// => Transition: SelectingPayment → ProcessingPayment
checkout2.handleEvent("confirm"); // => Execute payment via PayPal strategy
// => Processes events, triggers transitions
// => 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
// => Assign value
// => 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
// => Assign value
// => Defines event alphabet for FSM
// => Events trigger state transitions
interface Order {
// => Type declaration defines structure
// => Begin object/config definition
id: string; // => Statement execution
items: string[]; // => Statement execution
total: number; // => Statement execution
}
class OrderProcessor {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: OrderState = "Draft"; // => State field: stores current FSM state privately
// => Assign value
// => 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) {
// => Method invocation
// => Begin object/config definition
this.order = order; // => Statement execution
// => Access instance property
console.log(`Order ${order.id} created in Draft state`); // => Output for verification
// => Debug/audit output
// => Log for observability
}
getCurrentState(): OrderState {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns value to caller
// => Access instance property
// => Return current state value
}
getOrder(): Order {
// => Accessor method definition
// => Begin object/config definition
return this.order; // => Returns value to caller
// => Access instance property
// => Return computed result
}
handleEvent(event: OrderEvent): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
const prevState = this.state; // => State variable initialization
// => Access instance property
// => Initialize prevState
if (event === "submit" && this.state === "Draft") {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => Event type check
// => Combined (state, event) guard
this.state = "Submitted"; // => State transition execution
// => Access instance property
// => 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") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
this.state = "Confirmed"; // => State transition execution
// => Access instance property
// => 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") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
this.state = "Processing"; // => State transition execution
// => Access instance property
// => 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") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
this.state = "Shipped"; // => State transition execution
// => Access instance property
// => 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") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
this.state = "Delivered"; // => State transition execution
// => Access instance property
// => 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)) {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
this.state = "Cancelled"; // => State transition execution
// => Access instance property
// => 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 {
// => Statement execution
// => Fallback branch
throw new Error(`Invalid transition: ${event} not allowed in ${this.state} state`); // => Method invocation
// => 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", // => Statement execution
items: ["Widget A", "Widget B"], // => Statement execution
total: 99.99, // => Statement execution
}); // => Statement execution
// => Output: Order ORD-001 created in Draft state
order.handleEvent("submit"); // => Draft → Submitted
// => Processes events, triggers transitions
// => Output: Order submitted: 2 items, $99.99
// => Transition: Draft → Submitted
order.handleEvent("confirm"); // => Submitted → Confirmed
// => Processes events, triggers transitions
// => Output: Order confirmed: Payment authorized
// => Transition: Submitted → Confirmed
order.handleEvent("startProcessing"); // => Confirmed → Processing
// => Processes events, triggers transitions
// => Output: Processing: Preparing items for shipment
// => Transition: Confirmed → Processing
order.handleEvent("cancel"); // => Processing → Cancelled
// => Processes events, triggers transitions
// => Output: Order cancelled from Processing state
// => Transition: Processing → Cancelled
console.log(`Final state: ${order.getCurrentState()}`); // => Output: Final state: Cancelled
// => FSM state management logic
// => Pure read, no side effects
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.
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
// => Assign value
// => Enum-like union type for state values
// => Type system ensures only valid states used
type OrderEvent = "submit" | "confirm" | "ship"; // => Type declaration defines structure
// => Assign value
// => Defines event alphabet for FSM
// => Events trigger state transitions
interface OrderItem {
// => Type declaration defines structure
// => Begin object/config definition
sku: string; // => Statement execution
quantity: number; // => Statement execution
}
class InventorySystem {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private stock: Record<string, number> = {
// => Field declaration: class member variable
// => Assign value
// => Extended state (data beyond FSM state)
// => Initialized alongside FSM state
"SKU-A": 10, // => Statement execution
"SKU-B": 5, // => Statement execution
"SKU-C": 0, // => Out of stock
};
checkAvailability(items: OrderItem[]): boolean {
// => Method invocation
// => Begin object/config definition
for (const item of items) {
// => Method invocation
// => Iterate collection
// => Begin object/config definition
const available = this.stock[item.sku] || 0; // => Check stock
// => FSM state management logic
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 {
// => Method invocation
// => Begin object/config definition
for (const item of items) {
// => Method invocation
// => Iterate collection
// => Begin object/config definition
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 {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: OrderState = "Draft"; // => State field: stores current FSM state privately
// => Assign value
// => 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) {
// => Method invocation
// => Begin object/config definition
this.items = items; // => Statement execution
// => Access instance property
this.inventory = inventory; // => Statement execution
// => Access instance property
}
getCurrentState(): OrderState {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns value to caller
// => Access instance property
// => Return current state value
}
handleEvent(event: OrderEvent): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (event === "submit" && this.state === "Draft") {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => 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") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
// 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 {
// => Statement execution
// => 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") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
this.state = "Shipped"; // => Confirmed → Shipped
console.log("Order shipped"); // => Output for verification
// => Debug/audit output
// => Log for observability
} else {
// => Statement execution
// => 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
[
// => Statement execution
{ sku: "SKU-A", quantity: 2 }, // => Statement execution
{ sku: "SKU-B", quantity: 1 }, // => Statement execution
], // => Statement execution
inventory, // => Statement execution
); // => Statement execution
successOrder.handleEvent("submit"); // => Draft → Submitted
// => Processes events, triggers transitions
// => Output: Order submitted
successOrder.handleEvent("confirm"); // => Check inventory (passes)
// => FSM state management logic
// => Processes events, triggers transitions
// => Output: Reserved: 2 × SKU-A (remaining: 8)
// => Reserved: 1 × SKU-B (remaining: 4)
// => Order confirmed: Stock reserved
console.log(successOrder.getCurrentState()); // => Output: Confirmed
// => FSM state management logic
// => Pure read, no side effects
// 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
// => Processes events, triggers transitions
failOrder.handleEvent("confirm"); // => Check inventory (fails)
// => FSM state management logic
// => Processes events, triggers transitions
// => Output: Out of stock: SKU-C (need 1, have 0)
// => Order failed: Insufficient inventory
console.log(failOrder.getCurrentState()); // => Output: OutOfStock
// => FSM state management logic
// => Pure read, no side effects
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.
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
// => Assign value
// => Enum-like union type for state values
// => Type system ensures only valid states used
type OrderEvent = "authorize" | "timeout"; // => Type declaration defines structure
// => Assign value
// => Defines event alphabet for FSM
// => Events trigger state transitions
class TimedOrder {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: OrderState = "Submitted"; // => State field: stores current FSM state privately
// => Assign value
// => 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
// => Assign value
// => Extended state (data beyond FSM state)
// => Initialized alongside FSM state
constructor() {
// => Method invocation
// => Begin object/config definition
this.createdAt = Date.now(); // => Method invocation
// => Assign value
this.startPaymentTimeout(); // => Start timeout on creation
}
getCurrentState(): OrderState {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns value to caller
// => Access instance property
// => Return current state value
}
private startPaymentTimeout(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
this.timeoutHandle = setTimeout(() => {
// => Event handler method
// => Chained method calls or nested operations
// => Assign value
// => Begin object/config definition
if (this.state === "PaymentPending" || this.state === "Submitted") {
// => State-based guard condition
// => Logical OR: either condition can be true
// => Comparison check
// => 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
// => Processes events, triggers transitions
}
}, this.PAYMENT_TIMEOUT); // => Statement execution
// => Access instance property
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 {
// => Method invocation
// => Extended state (data beyond FSM state)
if (this.timeoutHandle) {
// => Event handler method
// => Conditional check
// => Branch execution based on condition
clearTimeout(this.timeoutHandle); // => Cancel timeout
this.timeoutHandle = null; // => Statement execution
// => Access instance property
console.log("Timeout cancelled"); // => Output for verification
// => Debug/audit output
// => Log for observability
}
}
handleEvent(event: OrderEvent): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (event === "authorize" && this.state === "Submitted") {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => 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") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
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)) {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
this.clearTimeoutIfNeeded(); // => Method invocation
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 {
// => Method invocation
// => Begin object/config definition
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
// => Processes events, triggers transitions
// => Output: Payment authorization started
// Authorize within timeout
setTimeout(() => {
// => Accessor method definition
// => Chained method calls or nested operations
// => Begin object/config definition
quickOrder.handleEvent("authorize"); // => PaymentPending → Confirmed
// => Processes events, triggers transitions
// => Output: Timeout cancelled
// => Payment confirmed
console.log(`State: ${quickOrder.getCurrentState()}`); // => Output: State: Confirmed
// => FSM state management logic
// => Pure read, no side effects
quickOrder.cleanup(); // => Method invocation
}, 2000); // => Statement execution
// 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(() => {
// => Accessor method definition
// => Chained method calls or nested operations
// => Begin object/config definition
console.log(`State: ${slowOrder.getCurrentState()}`); // => Output: State: Expired
// => FSM state management logic
// => Pure read, no side effects
slowOrder.cleanup(); // => Method invocation
}, 6000); // => Statement execution
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
// => Assign value
// => Enum-like union type for state values
// => Type system ensures only valid states used
type SagaEvent = "reserveInventory" | "chargePayment" | "complete" | "fail"; // => Type declaration defines structure
// => Assign value
// => Defines event alphabet for FSM
// => Events trigger state transitions
class OrderSaga {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: SagaState = "Initial"; // => State field: stores current FSM state privately
// => Assign value
// => 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 {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns value to caller
// => Access instance property
// => Return current state value
}
private recordCompensation(action: () => void): void {
// => Method invocation
// => 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 {
// => Method invocation
// => 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) {
// => Method invocation
// => Loop while condition true
// => Begin object/config definition
const compensate = this.compensations.pop()!; // => Get last compensation
compensate(); // => Execute compensation
}
}
handleEvent(event: SagaEvent): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (event === "reserveInventory" && this.state === "Initial") {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => 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(() => {
// => Method invocation
// => Chained method calls or nested operations
// => Begin object/config definition
console.log(" Compensating: Releasing inventory reservation"); // => Output for verification
// => Debug/audit output
// => Log for observability
}); // => Statement execution
console.log("Inventory reserved"); // => Output for verification
// => Debug/audit output
// => Log for observability
} else {
// => Statement execution
// => Fallback branch
this.handleEvent("fail"); // => Trigger failure
// => Processes events, triggers transitions
}
} else if (event === "chargePayment" && this.state === "InventoryReserved") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
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
// => Access instance property
// => Transition: set state to PaymentCharged
// => State mutation (core FSM operation)
this.recordCompensation(() => {
// => Method invocation
// => Chained method calls or nested operations
// => Begin object/config definition
console.log(" Compensating: Refunding payment"); // => Output for verification
// => Debug/audit output
// => Log for observability
}); // => Statement execution
console.log("Payment charged"); // => Output for verification
// => Debug/audit output
// => Log for observability
} else {
// => Statement execution
// => Fallback branch
console.log("Payment failed!"); // => Output for verification
// => Debug/audit output
// => Log for observability
this.handleEvent("fail"); // => Trigger failure (will compensate)
// => FSM state management logic
// => Processes events, triggers transitions
}
} else if (event === "complete" && this.state === "PaymentCharged") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
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") {
// => Method signature: defines function interface
// => Comparison check
// => Alternative condition
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
// => Processes events, triggers transitions
// => Output: Step 1: Reserving inventory...
// => Compensation recorded (total: 1)
// => Inventory reserved
saga.handleEvent("chargePayment"); // => Payment fails → Compensate
// => Processes events, triggers transitions
// => 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
// => FSM state management logic
// => Pure read, no side effects
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
// => Assign value
// => 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
// => Assign value
// => Defines event alphabet for FSM
// => Events trigger state transitions
interface User {
// => Type declaration defines structure
// => Begin object/config definition
id: string; // => Statement execution
token: string; // => Statement execution
refreshToken: string; // => Statement execution
}
class AuthenticationFlow {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: AuthState = "LoggedOut"; // => State field: stores current FSM state privately
// => Assign value
// => Mutable state storage (single source of truth)
// => FSM begins execution in LoggedOut state
private user: User | null = null; // => Field declaration: class member variable
// => Assign value
// => Extended state (data beyond FSM state)
// => Initialized alongside FSM state
private tokenRefreshHandle: NodeJS.Timeout | null = null; // => Field declaration: class member variable
// => Assign value
// => Extended state (data beyond FSM state)
// => Initialized alongside FSM state
getCurrentState(): AuthState {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns value to caller
// => Access instance property
// => Return current state value
}
getUser(): User | null {
// => Accessor method definition
// => Begin object/config definition
return this.user; // => Returns value to caller
// => Access instance property
// => Return computed result
}
private scheduleTokenRefresh(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
this.tokenRefreshHandle = setTimeout(() => {
// => Event handler method
// => Chained method calls or nested operations
// => Assign value
// => Begin object/config definition
console.log("Token expiring soon..."); // => Output for verification
// => Debug/audit output
// => Log for observability
this.handleEvent("tokenExpiring"); // => Auto-trigger refresh
// => Processes events, triggers transitions
}, 10000); // => Statement execution
console.log("Token refresh scheduled (10s)"); // => Output for verification
// => Chained method calls or nested operations
// => Debug/audit output
// => Log for observability
}
private clearTokenRefresh(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
if (this.tokenRefreshHandle) {
// => Event handler method
// => Conditional check
// => Branch execution based on condition
clearTimeout(this.tokenRefreshHandle); // => Event handler method
this.tokenRefreshHandle = null; // => Statement execution
// => Access instance property
}
}
handleEvent(event: AuthEvent, data?: any): void {
// => Event handler method
// => Ternary: condition ? true_branch : false_branch
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (event === "login" && this.state === "LoggedOut") {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => 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(() => {
// => Accessor method definition
// => Chained method calls or nested operations
// => Begin object/config definition
const success = true; // => Simulate success
if (success) {
// => Conditional branch
// => Conditional check
// => Branch execution based on condition
this.handleEvent("success", {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
id: "user123", // => Statement execution
token: "jwt-token", // => Statement execution
refreshToken: "refresh-token", // => Statement execution
}); // => Statement execution
} else {
// => Statement execution
// => Fallback branch
this.handleEvent("failure"); // => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
}
}, 1000); // => Statement execution
} else if (event === "success" && this.state === "Authenticating") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
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") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
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") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
this.state = "RefreshingToken"; // => LoggedIn → RefreshingToken
console.log("Refreshing authentication token..."); // => Output for verification
// => Debug/audit output
// => Log for observability
// Simulate token refresh
setTimeout(() => {
// => Accessor method definition
// => Chained method calls or nested operations
// => Begin object/config definition
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 method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
} else {
// => Statement execution
// => Fallback branch
this.handleEvent("refreshFailed"); // => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
}
}, 1000); // => Statement execution
} else if (event === "tokenRefreshed" && this.state === "RefreshingToken") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
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") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
this.user = null; // => Clear user
this.clearTokenRefresh(); // => Method invocation
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") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
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 {
// => Method invocation
// => Begin object/config definition
this.clearTokenRefresh(); // => Cleanup timers
}
}
// Usage
const auth = new AuthenticationFlow(); // => state: LoggedOut
console.log(`Initial state: ${auth.getCurrentState()}`); // => Output: Initial state: LoggedOut
// => FSM state management logic
// => Pure read, no side effects
auth.handleEvent("login"); // => LoggedOut → Authenticating
// => Processes events, triggers transitions
// => 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(() => {
// => Accessor method definition
// => Chained method calls or nested operations
// => Begin object/config definition
auth.handleEvent("logout"); // => LoggedIn → LoggedOut
// => Processes events, triggers transitions
// => Output: User logged out
console.log(`State: ${auth.getCurrentState()}`); // => Output: State: LoggedOut
// => FSM state management logic
// => Pure read, no side effects
auth.cleanup(); // => Method invocation
}, 15000); // => Statement execution
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
// => Assign value
// => 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
// => Assign value
// => Defines event alphabet for FSM
// => Events trigger state transitions
class MFAFlow {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: MFAState = "LoggedOut"; // => State field: stores current FSM state privately
// => Assign value
// => Mutable state storage (single source of truth)
// => FSM begins execution in LoggedOut state
private userId: string | null = null; // => Field declaration: class member variable
// => Assign value
// => Extended state (data beyond FSM state)
// => Initialized alongside FSM state
private mfaAttempts = 0; // => Statement execution
// => Assign value
// => Extended state (data beyond FSM state)
// => Initialized alongside FSM state
private readonly MAX_MFA_ATTEMPTS = 3; // => Statement execution
// => Assign value
// => Extended state (data beyond FSM state)
// => Initialized alongside FSM state
getCurrentState(): MFAState {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns value to caller
// => Access instance property
// => Return current state value
}
handleEvent(event: MFAEvent, data?: any): void {
// => Event handler method
// => Ternary: condition ? true_branch : false_branch
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (event === "submitPassword" && this.state === "LoggedOut") {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => 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 {
// => Statement execution
// => Fallback branch
console.log("Invalid password"); // => Output for verification
// => Debug/audit output
// => Log for observability
}
} else if (event === "requestMFA" && this.state === "PasswordVerified") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
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") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
const codeValid = data.code === "123456"; // => Simulate verification
if (codeValid) {
// => Conditional branch
// => Conditional check
// => Branch execution based on condition
this.handleEvent("mfaSuccess"); // => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
} else {
// => Statement execution
// => 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
// => Comparison check
// => Conditional check
// => Branch execution based on condition
this.handleEvent("mfaFail"); // => Too many attempts
// => Processes events, triggers transitions
} else {
// => Statement execution
// => Fallback branch
console.log("Try again."); // => Output for verification
// => Debug/audit output
// => Log for observability
}
}
} else if (event === "mfaSuccess" && this.state === "AwaitingMFA") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
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") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
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") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
this.userId = null; // => Statement execution
// => Access instance property
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
// => FSM state management logic
// => Processes events, triggers transitions
// => Output: Password verified. MFA required.
mfa.handleEvent("requestMFA"); // => PasswordVerified → AwaitingMFA
// => Processes events, triggers transitions
// => Output: MFA code sent to device. Awaiting verification...
mfa.handleEvent("submitMFA", { code: "123456" }); // => Correct code → LoggedIn
// => FSM state management logic
// => Processes events, triggers transitions
// => Output: MFA successful. User user123 logged in.
console.log(`State: ${mfa.getCurrentState()}`); // => Output: State: LoggedIn
// => FSM state management logic
// => Pure read, no side effects
// 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 method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
mfa2.handleEvent("requestMFA"); // => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
mfa2.handleEvent("submitMFA", { code: "wrong1" }); // => Attempt 1
// => FSM state management logic
// => Processes events, triggers transitions
// => Output: MFA failed. Attempts: 1/3
mfa2.handleEvent("submitMFA", { code: "wrong2" }); // => Attempt 2
// => FSM state management logic
// => Processes events, triggers transitions
// => Output: MFA failed. Attempts: 2/3
mfa2.handleEvent("submitMFA", { code: "wrong3" }); // => Attempt 3 → Lockout
// => FSM state management logic
// => Processes events, triggers transitions
// => Output: MFA failed. Attempts: 3/3
// => MFA lockout: Too many failed attempts
console.log(`State: ${mfa2.getCurrentState()}`); // => Output: State: LoggedOut
// => FSM state management logic
// => Pure read, no side effects
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
// => Assign value
// => 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
// => Assign value
// => Defines event alphabet for FSM
// => Events trigger state transitions
class SessionManager {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: SessionState = "LoggedOut"; // => State field: stores current FSM state privately
// => Assign value
// => Mutable state storage (single source of truth)
// => FSM begins execution in LoggedOut state
private activityTimeout: NodeJS.Timeout | null = null; // => Field declaration: class member variable
// => Assign value
// => 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 {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns value to caller
// => Access instance property
// => Return current state value
}
private resetActivityTimer(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
this.clearActivityTimer(); // => Clear existing timer
this.activityTimeout = setTimeout(() => {
// => Method invocation
// => Chained method calls or nested operations
// => Assign value
// => Begin object/config definition
console.log("Session idle timeout"); // => Output for verification
// => Debug/audit output
// => Log for observability
this.handleEvent("timeout"); // => Auto-trigger timeout
// => Processes events, triggers transitions
}, this.IDLE_TIMEOUT); // => Statement execution
// => Access instance property
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 {
// => Method invocation
// => Extended state (data beyond FSM state)
if (this.activityTimeout) {
// => Conditional branch
// => Conditional check
// => Branch execution based on condition
clearTimeout(this.activityTimeout); // => Method invocation
this.activityTimeout = null; // => Statement execution
// => Access instance property
}
}
handleEvent(event: SessionEvent): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (event === "login" && this.state === "LoggedOut") {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => 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") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
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") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
this.state = "Idle"; // => Active → Idle (no activity)
console.log("Session idle: Limited access"); // => Output for verification
// => Debug/audit output
// => Log for observability
this.clearActivityTimer(); // => Method invocation
} else if (event === "activity" && this.state === "Idle") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
this.state = "Active"; // => Idle → Active (activity resumes)
console.log("Activity resumed: Session active"); // => Output for verification
// => Debug/audit output
// => Log for observability
this.resetActivityTimer(); // => Method invocation
} else if (event === "sensitiveOp" && this.state === "Active") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
this.state = "RequiresReauth"; // => Active → RequiresReauth
console.log("Sensitive operation: Re-authentication required"); // => Output for verification
// => Debug/audit output
// => Log for observability
this.clearActivityTimer(); // => Method invocation
} else if (event === "sensitiveOp" && this.state === "Idle") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
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") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
this.state = "Reauthenticating"; // => RequiresReauth → Reauthenticating
console.log("Re-authenticating..."); // => Output for verification
// => Debug/audit output
// => Log for observability
// Simulate re-auth
setTimeout(() => {
// => Accessor method definition
// => Chained method calls or nested operations
// => Begin object/config definition
this.handleEvent("reauthSuccess"); // => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
}, 1000); // => Statement execution
} else if (event === "reauthSuccess" && this.state === "Reauthenticating") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Alternative condition
this.state = "Active"; // => Reauthenticating → Active
console.log("Re-authentication successful"); // => Output for verification
// => Debug/audit output
// => Log for observability
this.resetActivityTimer(); // => Method invocation
} else if (event === "logout") {
// => Method signature: defines function interface
// => Comparison check
// => Alternative condition
this.clearActivityTimer(); // => Method invocation
this.state = "LoggedOut"; // => Any state → LoggedOut
console.log("User logged out"); // => Output for verification
// => Debug/audit output
// => Log for observability
}
}
cleanup(): void {
// => Method invocation
// => Begin object/config definition
this.clearActivityTimer(); // => Method invocation
}
}
// Usage
const session = new SessionManager(); // => state: LoggedOut
session.handleEvent("login"); // => LoggedOut → Active
// => Processes events, triggers transitions
// => Output: User logged in
// => Activity timer reset (5000ms)
session.handleEvent("activity"); // => Reset idle timer
// => Processes events, triggers transitions
// => Output: User activity detected
// => Activity timer reset (5000ms)
// Wait for idle timeout
setTimeout(() => {
// => Accessor method definition
// => Chained method calls or nested operations
// => Begin object/config definition
// After 5 seconds idle:
// => Output: Session idle timeout
// => Session idle: Limited access
console.log(`State: ${session.getCurrentState()}`); // => Output: State: Idle
// => FSM state management logic
// => Pure read, no side effects
// Try sensitive operation
session.handleEvent("sensitiveOp"); // => Idle → RequiresReauth
// => Processes events, triggers transitions
// => Output: Sensitive operation from idle: Re-authentication required
session.handleEvent("reauth"); // => RequiresReauth → Reauthenticating
// => Processes events, triggers transitions
// => Output: Re-authenticating...
// After 1 second:
// => Output: Re-authentication successful
// => Activity timer reset (5000ms)
setTimeout(() => {
// => Accessor method definition
// => Chained method calls or nested operations
// => Begin object/config definition
session.handleEvent("logout"); // => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
session.cleanup(); // => Method invocation
}, 2000); // => Statement execution
}, 6000); // => Statement execution
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.