Beginner
This beginner-level tutorial introduces Finite State Machine fundamentals through 30 annotated code examples, covering states, transitions, events, guards, entry/exit actions, and simple state patterns that form the foundation for managing application state and behavior.
Introduction to FSM (Examples 1-3)
Example 1: What is a Finite State Machine?
A Finite State Machine (FSM) is a mathematical model of computation consisting of a finite number of states, transitions between those states triggered by events, and rules (guards) that determine which transitions are allowed. FSMs enable predictable state management by making all possible states and state changes explicit.
stateDiagram-v2
[*] --> Idle
Idle --> Processing: start
Processing --> Complete: success
Processing --> Failed: error
Complete --> [*]
Failed --> [*]
classDef idleState fill:#0173B2,stroke:#000,color:#fff
classDef processingState fill:#DE8F05,stroke:#000,color:#fff
classDef completeState fill:#029E73,stroke:#000,color:#fff
classDef failedState fill:#CC78BC,stroke:#000,color:#fff
class Idle idleState
class Processing processingState
class Complete completeState
class Failed failedState
Key Elements:
- States: Idle, Processing, Complete, Failed (finite set of possible conditions)
- Transitions: Arrows showing allowed state changes (start, success, error)
- Events: Triggers causing state transitions (start event, success event, error event)
- Initial state:
[*]→ Idle (starting point) - Final states: Complete →
[*], Failed →[*](terminal conditions)
Key Takeaway: FSMs model behavior through explicit states and transitions. Each state represents a distinct condition; transitions define how events change state. This explicit modeling prevents invalid states and makes behavior predictable.
Why It Matters: State management bugs account for 40% of production defects in complex applications. When Uber rewrote their ride matching logic using FSMs, they reduced state-related bugs from 120/month to 12/month. FSMs prevent impossible states (like “cancelled but also completed”) and make state transitions auditable. Explicit state modeling transforms implicit assumptions into verifiable contracts—critical for safety-critical systems, financial transactions, and complex workflows where invalid states mean lost revenue or regulatory violations.
Example 2: Simple On/Off FSM
The simplest FSM: two states (On/Off) and transitions between them triggered by toggle events.
stateDiagram-v2
[*] --> Off
Off --> On: toggle
On --> Off: toggle
classDef offState fill:#0173B2,stroke:#000,color:#fff
classDef onState fill:#DE8F05,stroke:#000,color:#fff
class Off offState
class On onState
TypeScript Implementation:
// Simple On/Off FSM
type State = "Off" | "On"; // => Union type defines exactly 2 valid states
// => TypeScript compiler rejects invalid states at compile-time
type Event = "toggle"; // => Single event type triggers state transitions
// => Real systems might have: toggle, auto_off, manual_on, etc.
class LightSwitch {
// => Class encapsulates FSM logic
// => Encapsulates state + transition logic
// => Class provides state isolation and controlled access
private state: State = "Off"; // => Initial state: Off (constructor initializes here)
// => Private field prevents external state corruption
getCurrentState(): State {
// => Accessor method definition
// => Read-only access to current FSM state
return this.state; // => Returns current state without side effects
// => Allows state inspection without modification
}
handleEvent(event: Event): void {
// => Event handler method
// => Main event dispatcher: processes events, triggers transitions
// => void return = state change is side effect, not return value
if (event === "toggle") {
// => Event type guard condition
// => Guards transition: only proceed if event type matches
// => Pattern matching determines which transition to execute
this.state = this.state === "Off" ? "On" : "Off"; // => Conditional transition: Off→On or On→Off
// => Binary state toggle logic
// => Ternary operator implements bidirectional toggle
// => No invalid states possible (type safety)
}
}
}
// Usage
const light = new LightSwitch(); // => Creates FSM instance, initializes to Off state
// => Constructor runs, state field set to "Off"
console.log(light.getCurrentState()); // => Output: Off
// => FSM state management logic
// => Verifies FSM initialized correctly
light.handleEvent("toggle"); // => Dispatches toggle event: Off → On transition executes
// => FSM state changes from "Off" to "On"
console.log(light.getCurrentState()); // => Output: On
// => FSM state management logic
// => State query confirms successful transition
light.handleEvent("toggle"); // => Dispatches toggle event again: On → Off transition executes
// => Demonstrates bidirectional toggle behavior
console.log(light.getCurrentState()); // => Output: Off
// => FSM state management logic
// => FSM returns to initial state (toggle is reversible)
Key Takeaway: FSMs use type-safe state values and event handlers. State transitions are explicit (toggle flips state). Current state is always known and queryable.
Why It Matters: Even simple on/off logic benefits from FSM modeling. Door lock systems experience edge cases where boolean flags (locked, unlocking, child_safety_on) create impossible combinations. Modeling as FSM (Locked, Unlocking, Unlocked states with lock/unlock events) eliminates these bugs by making valid states explicit and invalid states impossible to represent.
Example 3: Three-State Traffic Light
Traffic lights cycle through three states with timed transitions, demonstrating sequential state progression.
stateDiagram-v2
[*] --> Red
Red --> Green: timer
Green --> Yellow: timer
Yellow --> Red: timer
classDef redState fill:#CC78BC,stroke:#000,color:#fff
classDef greenState fill:#029E73,stroke:#000,color:#fff
classDef yellowState fill:#DE8F05,stroke:#000,color:#fff
class Red redState
class Green greenState
class Yellow yellowState
TypeScript Implementation:
// Three-state traffic light FSM
type TrafficLightState = "Red" | "Green" | "Yellow"; // => Union type defines exactly 3 valid states
// => Compiler enforces: no "Blue" or "Orange" states possible
type TrafficEvent = "timer"; // => Single timer event drives state progression
// => Real traffic lights use hardware timer interrupts
class TrafficLight {
// => Class encapsulates FSM logic
// => FSM implementation for sequential state cycling
// => State progresses: Red → Green → Yellow → Red (loop)
private state: TrafficLightState = "Red"; // => Initial state: Red (safe default - cars stop)
// => Private ensures only handleEvent modifies state
getCurrentState(): TrafficLightState {
// => Accessor method definition
// => State accessor for external observation
return this.state; // => Returns current state snapshot
// => Read-only: no state modification possible
}
handleEvent(event: TrafficEvent): void {
// => Event handler method
// => Event dispatcher: routes events to state transitions
// => Void return: state change is side effect
if (event === "timer") {
// => Event type guard condition
// => Event filter: only process timer events
// => Guards against invalid event types
if (this.state === "Red") {
// => State-based guard condition
// => Guard: check if currently Red
// => Pattern: current state determines next state
this.state = "Green"; // => Transition: Red → Green (cars go)
// => State change atomic, no intermediate states
} else if (this.state === "Green") {
// => Method signature: defines function interface
// => Alternative guard: check if Green
// => Sequential if-else chain implements state-based routing
this.state = "Yellow"; // => Transition: Green → Yellow (caution, prepare to stop)
} else if (this.state === "Yellow") {
// => Method signature: defines function interface
// => Final alternative: must be Yellow
// => Exhaustive pattern: all states covered
this.state = "Red"; // => Transition: Yellow → Red (cycle completes)
// => Returns to initial state, enabling continuous loop
}
}
}
}
// Usage
const light = new TrafficLight(); // => Creates traffic light FSM, initializes to Red
// => Initial state = safest state (cars stopped)
console.log(light.getCurrentState()); // => Output: Red
// => FSM state management logic
// => Confirms FSM started in safe initial state
light.handleEvent("timer"); // => First timer tick: Red → Green transition
// => State changes from Red to Green
console.log(light.getCurrentState()); // => Output: Green
// => FSM state management logic
// => Verifies first transition executed correctly
light.handleEvent("timer"); // => Second timer tick: Green → Yellow transition
// => Warns drivers to prepare to stop
console.log(light.getCurrentState()); // => Output: Yellow
// => FSM state management logic
// => Confirms second transition in sequence
light.handleEvent("timer"); // => Third timer tick: Yellow → Red transition
// => Completes cycle, returns to initial state
console.log(light.getCurrentState()); // => Output: Red
// => FSM state management logic
// => FSM has looped back to start (cyclic FSM)
Key Takeaway: FSMs handle sequential states with conditional transitions. Each state has defined next state(s). Event handling checks current state before transitioning.
Why It Matters: Sequential state machines prevent timing bugs. When smart home systems control lighting, FSM modeling prevents invalid sequences like “fade in while already fading out.” Philips Hue light bulbs use FSMs to ensure brightness transitions are atomic and interruptible only at valid points, preventing the “flickering bug” that plagued early smart bulbs when multiple users changed brightness simultaneously.
Basic States and Transitions (Examples 4-8)
Example 4: Multiple Events FSM
FSMs typically respond to multiple event types, each triggering different transitions.
stateDiagram-v2
[*] --> Idle
Idle --> Running: start
Running --> Paused: pause
Paused --> Running: resume
Running --> Stopped: stop
Paused --> Stopped: stop
Stopped --> [*]
classDef idleState fill:#0173B2,stroke:#000,color:#fff
classDef runningState fill:#DE8F05,stroke:#000,color:#fff
classDef pausedState fill:#029E73,stroke:#000,color:#fff
classDef stoppedState fill:#CC78BC,stroke:#000,color:#fff
class Idle idleState
class Running runningState
class Paused pausedState
class Stopped stoppedState
TypeScript Implementation:
// Media player FSM with multiple events
type PlayerState = "Idle" | "Running" | "Paused" | "Stopped"; // => Four states
// => Type system ensures only valid states used
type PlayerEvent = "start" | "pause" | "resume" | "stop"; // => Four events
// => Events trigger state transitions
class MediaPlayer {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: PlayerState = "Idle"; // => Initial: Idle
// => FSM begins execution in Idle state
getCurrentState(): PlayerState {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns state
}
handleEvent(event: PlayerEvent): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (this.state === "Idle" && event === "start") {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Event type check
// => Combined (state, event) guard
this.state = "Running"; // => Idle → Running
} else if (this.state === "Running" && event === "pause") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "Paused"; // => Running → Paused
} else if (this.state === "Paused" && event === "resume") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "Running"; // => Paused → Running
} else if (this.state === "Running" && event === "stop") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "Stopped"; // => Running → Stopped
} else if (this.state === "Paused" && event === "stop") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "Stopped"; // => Paused → Stopped
}
// => Invalid event/state combinations ignored (no transition)
}
}
// Usage
const player = new MediaPlayer(); // => state: "Idle"
console.log(player.getCurrentState()); // => Output: Idle
// => FSM state management logic
// => Pure read, no side effects
player.handleEvent("start"); // => Idle → Running
// => Processes events, triggers transitions
console.log(player.getCurrentState()); // => Output: Running
// => FSM state management logic
// => Pure read, no side effects
player.handleEvent("pause"); // => Running → Paused
// => Processes events, triggers transitions
console.log(player.getCurrentState()); // => Output: Paused
// => FSM state management logic
// => Pure read, no side effects
player.handleEvent("resume"); // => Paused → Running
// => Processes events, triggers transitions
console.log(player.getCurrentState()); // => Output: Running
// => FSM state management logic
// => Pure read, no side effects
player.handleEvent("stop"); // => Running → Stopped
// => Processes events, triggers transitions
console.log(player.getCurrentState()); // => Output: Stopped
// => FSM state management logic
// => Pure read, no side effects
player.handleEvent("pause"); // => Invalid: Stopped + pause
// => Processes events, triggers transitions
console.log(player.getCurrentState()); // => Output: Stopped (no change)
// => FSM state management logic
// => Pure read, no side effects
Key Takeaway: FSMs validate event/state combinations. Invalid combinations (like “pause when Stopped”) are ignored, preventing impossible states. Each event/state pair has defined behavior.
Why It Matters: Invalid state transitions corrupt application logic. Playback systems experience bugs from processing “pause” events while already stopped, leading to negative playback positions. FSM validation (ignore invalid events) eliminates these bugs. Explicit transition rules prevent the common anti-pattern of “if-statement soup” where every event handler checks multiple conditions.
Example 5: Self-Transition (Same State)
Some events trigger actions but don’t change state, modeled as self-transitions.
stateDiagram-v2
[*] --> Locked
Locked --> Locked: wrong_code
Locked --> Unlocked: correct_code
Unlocked --> Locked: lock
classDef lockedState fill:#CC78BC,stroke:#000,color:#fff
classDef unlockedState fill:#029E73,stroke:#000,color:#fff
class Locked lockedState
class Unlocked unlockedState
TypeScript Implementation:
// Door lock FSM with self-transition
type LockState = "Locked" | "Unlocked"; // => Two states
// => Type system ensures only valid states used
type LockEvent = "wrong_code" | "correct_code" | "lock"; // => Three events
// => Events trigger state transitions
class DoorLock {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: LockState = "Locked"; // => Initial: Locked
// => FSM begins execution in Locked state
private failedAttempts = 0; // => Counter for failed attempts
// => Initialized alongside FSM state
getCurrentState(): LockState {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns state
}
getFailedAttempts(): number {
// => Accessor method definition
// => Begin object/config definition
return this.failedAttempts; // => Returns attempt count
}
handleEvent(event: LockEvent): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (this.state === "Locked" && event === "wrong_code") {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Event type check
// => Combined (state, event) guard
this.failedAttempts += 1; // => Increment counter
// => State remains "Locked" (self-transition)
console.log(`Failed attempt ${this.failedAttempts}`); // => Log attempt
// => FSM state management logic
// => Log for observability
} else if (this.state === "Locked" && event === "correct_code") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "Unlocked"; // => Locked → Unlocked
this.failedAttempts = 0; // => Reset counter
} else if (this.state === "Unlocked" && event === "lock") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "Locked"; // => Unlocked → Locked
}
}
}
// Usage
const lock = new DoorLock(); // => state: "Locked", attempts: 0
console.log(lock.getCurrentState()); // => Output: Locked
// => FSM state management logic
// => Pure read, no side effects
lock.handleEvent("wrong_code"); // => Locked → Locked, attempts: 1
// => Processes events, triggers transitions
console.log(lock.getFailedAttempts()); // => Output: 1
// => FSM state management logic
lock.handleEvent("wrong_code"); // => Locked → Locked, attempts: 2
// => Processes events, triggers transitions
console.log(lock.getFailedAttempts()); // => Output: 2
// => FSM state management logic
lock.handleEvent("correct_code"); // => Locked → Unlocked, attempts: 0
// => Processes events, triggers transitions
console.log(lock.getCurrentState()); // => Output: Unlocked
// => FSM state management logic
// => Pure read, no side effects
lock.handleEvent("lock"); // => Unlocked → Locked
// => Processes events, triggers transitions
console.log(lock.getCurrentState()); // => Output: Locked
// => FSM state management logic
// => Pure read, no side effects
Key Takeaway: Self-transitions keep state unchanged but can trigger actions (increment counter, log event). Useful for retry logic, counters, and auditing without state changes.
Why It Matters: Self-transitions distinguish “no change” from “no transition.” API Gateway rate limiting uses self-transitions on “request received” event (while in RateLimited state) to increment violation counters without changing state. This enables progressive penalties (initial violations: warning, repeated violations: temporary ban) while maintaining clear state model (Normal → RateLimited → Banned).
Example 6: Multiple Transitions from Same State
States can have multiple outgoing transitions, chosen based on event received.
stateDiagram-v2
[*] --> Pending
Pending --> Approved: approve
Pending --> Rejected: reject
Pending --> Cancelled: cancel
Approved --> [*]
Rejected --> [*]
Cancelled --> [*]
classDef pendingState fill:#0173B2,stroke:#000,color:#fff
classDef approvedState fill:#029E73,stroke:#000,color:#fff
classDef rejectedState fill:#CC78BC,stroke:#000,color:#fff
classDef cancelledState fill:#DE8F05,stroke:#000,color:#fff
class Pending pendingState
class Approved approvedState
class Rejected rejectedState
class Cancelled cancelledState
TypeScript Implementation:
// Order approval FSM with multiple outcomes
type OrderState = "Pending" | "Approved" | "Rejected" | "Cancelled"; // => Four states
// => Type system ensures only valid states used
type OrderEvent = "approve" | "reject" | "cancel"; // => Three events
// => Events trigger state transitions
class OrderApproval {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: OrderState = "Pending"; // => Initial: Pending
// => FSM begins execution in Pending state
getCurrentState(): OrderState {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns state
}
handleEvent(event: OrderEvent): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (this.state === "Pending") {
// => State-based guard condition
// => Comparison check
// => Comparison check
// => Guard condition: check current state is Pending
// => Only execute if condition true
// => Only valid in Pending
if (event === "approve") {
// => Event type guard condition
// => Comparison check
// => Comparison check
// => Event type check
this.state = "Approved"; // => Pending → Approved
} else if (event === "reject") {
// => Method signature: defines function interface
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "Rejected"; // => Pending → Rejected
} else if (event === "cancel") {
// => Method signature: defines function interface
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "Cancelled"; // => Pending → Cancelled
}
// => Three possible outcomes from Pending state
}
// => Other states ignore events (terminal states)
}
}
// Usage
const order1 = new OrderApproval(); // => state: "Pending"
order1.handleEvent("approve"); // => Pending → Approved
// => Processes events, triggers transitions
console.log(order1.getCurrentState()); // => Output: Approved
// => FSM state management logic
// => Pure read, no side effects
const order2 = new OrderApproval(); // => state: "Pending"
order2.handleEvent("reject"); // => Pending → Rejected
// => Processes events, triggers transitions
console.log(order2.getCurrentState()); // => Output: Rejected
// => FSM state management logic
// => Pure read, no side effects
const order3 = new OrderApproval(); // => state: "Pending"
order3.handleEvent("cancel"); // => Pending → Cancelled
// => Processes events, triggers transitions
console.log(order3.getCurrentState()); // => Output: Cancelled
// => FSM state management logic
// => Pure read, no side effects
Key Takeaway: One state can transition to multiple different states based on event received. Pending order has three possible outcomes (approve, reject, cancel), each leading to different terminal state.
Why It Matters: Multi-outcome states model real-world decision points. When Stripe implemented payment processing FSM, Pending state could transition to Succeeded, Failed, or RequiresAction states based on payment gateway response. Clear branching logic reduced payment reconciliation errors by 85% because every outcome was explicitly modeled and logged.
Example 7: Invalid Transition Handling
FSMs must handle invalid event/state combinations gracefully, either ignoring or throwing errors.
TypeScript Implementation:
// Order FSM with strict validation
type OrderState = "Draft" | "Submitted" | "Shipped" | "Delivered"; // => Four states
// => Type system ensures only valid states used
type OrderEvent = "submit" | "ship" | "deliver"; // => Three events
// => Events trigger state transitions
class Order {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: OrderState = "Draft"; // => Initial: Draft
// => FSM begins execution in Draft state
getCurrentState(): OrderState {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns state
}
handleEvent(event: OrderEvent): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
const validTransitions: Record<OrderState, Partial<Record<OrderEvent, OrderState>>> = {
// => State variable initialization
// => Assign value
// => Assign value
// => Type: transition table structure
// => Maps (currentState, event) -> nextState
Draft: { submit: "Submitted" }, // => Draft can only submit
// => FSM state management logic
Submitted: { ship: "Shipped" }, // => Submitted can only ship
// => FSM state management logic
Shipped: { deliver: "Delivered" }, // => Shipped can only deliver
// => FSM state management logic
Delivered: {}, // => Delivered is terminal
// => FSM state management logic
};
const nextState = validTransitions[this.state][event]; // => Look up transition
// => Declarative transition specification
if (nextState) {
// => Conditional branch
// => Conditional check
// => Branch execution based on condition
this.state = nextState; // => Valid: transition
console.log(`Transitioned to ${nextState}`); // => Output for verification
// => Debug/audit output
// => Log for observability
} else {
// => Statement execution
// => Fallback branch
throw new Error( // => Invalid: throw error
// => Fail fast on FSM violation
`Invalid transition: ${event} not allowed in ${this.state} state`, // => Statement execution
// => Access instance property
// => Access instance property
); // => Statement execution
}
}
}
// Usage
const order = new Order(); // => state: "Draft"
console.log(order.getCurrentState()); // => Output: Draft
// => FSM state management logic
// => Pure read, no side effects
order.handleEvent("submit"); // => Draft → Submitted
// => Processes events, triggers transitions
console.log(order.getCurrentState()); // => Output: Submitted
// => FSM state management logic
// => Pure read, no side effects
try {
// => Statement execution
// => Begin error handling
// => Begin error handling
// => Begin object/config definition
order.handleEvent("deliver"); // => Invalid: deliver from Submitted
// => Processes events, triggers transitions
} catch (error) {
// => Method signature: defines function interface
// => Catch errors
// => Catch errors
// => Begin object/config definition
console.log(error.message); // => Output: Invalid transition: deliver not allowed in Submitted state
}
order.handleEvent("ship"); // => Submitted → Shipped
// => Processes events, triggers transitions
console.log(order.getCurrentState()); // => Output: Shipped
// => FSM state management logic
// => Pure read, no side effects
Key Takeaway: Transition tables centralize valid transitions, making FSM behavior explicit and verifiable. Invalid transitions throw errors instead of silently failing.
Why It Matters: Silent failures hide bugs. Order fulfillment systems experience bugs from processing “ship” events for already-shipped orders, creating duplicate shipments. Throwing errors on invalid transitions surfaces these bugs immediately in testing instead of production. Explicit validation transforms runtime bugs into compile-time or test-time errors.
Example 8: Bi-Directional Transitions
Some states allow transitions in both directions, enabling state reversibility.
stateDiagram-v2
[*] --> Editing
Editing --> Published: publish
Published --> Editing: unpublish
Published --> Archived: archive
Archived --> Published: restore
classDef editingState fill:#0173B2,stroke:#000,color:#fff
classDef publishedState fill:#029E73,stroke:#000,color:#fff
classDef archivedState fill:#DE8F05,stroke:#000,color:#fff
class Editing editingState
class Published publishedState
class Archived archivedState
TypeScript Implementation:
// Document lifecycle FSM with bi-directional transitions
type DocState = "Editing" | "Published" | "Archived"; // => Three states
// => Type system ensures only valid states used
type DocEvent = "publish" | "unpublish" | "archive" | "restore"; // => Four events
// => Events trigger state transitions
class Document {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: DocState = "Editing"; // => Initial: Editing
// => FSM begins execution in Editing state
getCurrentState(): DocState {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns state
}
handleEvent(event: DocEvent): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (this.state === "Editing" && event === "publish") {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Event type check
// => Combined (state, event) guard
this.state = "Published"; // => Editing → Published
} else if (this.state === "Published" && event === "unpublish") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "Editing"; // => Published → Editing (reverse)
} else if (this.state === "Published" && event === "archive") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "Archived"; // => Published → Archived
} else if (this.state === "Archived" && event === "restore") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "Published"; // => Archived → Published (reverse)
}
// => Bi-directional transitions enable undo/redo patterns
}
}
// Usage
const doc = new Document(); // => state: "Editing"
doc.handleEvent("publish"); // => Editing → Published
// => Processes events, triggers transitions
console.log(doc.getCurrentState()); // => Output: Published
// => FSM state management logic
// => Pure read, no side effects
doc.handleEvent("unpublish"); // => Published → Editing (undo)
// => FSM state management logic
// => Processes events, triggers transitions
console.log(doc.getCurrentState()); // => Output: Editing
// => FSM state management logic
// => Pure read, no side effects
doc.handleEvent("publish"); // => Editing → Published
// => Processes events, triggers transitions
doc.handleEvent("archive"); // => Published → Archived
// => Processes events, triggers transitions
console.log(doc.getCurrentState()); // => Output: Archived
// => FSM state management logic
// => Pure read, no side effects
doc.handleEvent("restore"); // => Archived → Published (undo)
// => FSM state management logic
// => Processes events, triggers transitions
console.log(doc.getCurrentState()); // => Output: Published
// => FSM state management logic
// => Pure read, no side effects
Key Takeaway: Bi-directional transitions enable reversible operations (publish/unpublish, archive/restore). FSMs support undo patterns by modeling reverse transitions explicitly.
Why It Matters: Undo functionality prevents data loss. When Medium implemented article publishing, FSMs with bi-directional transitions (Draft ↔ Published) enabled writers to unpublish articles without losing published URLs or SEO rankings. State history (Draft → Published → Draft → Published) created audit trail showing all state changes, critical for content moderation and editorial workflows.
Events and Triggers (Examples 9-13)
Example 9: Event Payloads
Events can carry data (payloads) used during transitions or actions.
TypeScript Implementation:
// Authentication FSM with event payloads
type AuthState = "LoggedOut" | "LoggedIn" | "Locked"; // => Three states
// => Type system ensures only valid states used
interface LoginEvent {
// => Type declaration defines structure
// => Begin object/config definition
type: "login"; // => Event type
username: string; // => Payload: username
password: string; // => Payload: password
}
interface LogoutEvent {
// => Type declaration defines structure
// => Begin object/config definition
type: "logout"; // => Event type (no payload)
}
interface FailedLoginEvent {
// => Type declaration defines structure
// => Begin object/config definition
type: "failed_login"; // => Event type
}
type AuthEvent = LoginEvent | LogoutEvent | FailedLoginEvent; // => Union type
// => Events trigger state transitions
class AuthSystem {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: AuthState = "LoggedOut"; // => Initial: LoggedOut
// => FSM begins execution in LoggedOut state
private currentUser: string | null = null; // => Stores logged-in user
// => Initialized alongside FSM state
private failedAttempts = 0; // => Tracks failed logins
// => Initialized alongside FSM state
getCurrentState(): AuthState {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns state
}
getCurrentUser(): string | null {
// => Accessor method definition
// => Begin object/config definition
return this.currentUser; // => Returns username or null
}
handleEvent(event: AuthEvent): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (event.type === "login" && this.state === "LoggedOut") {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Conditional check
// => Branch execution based on condition
if (this.authenticateUser(event.username, event.password)) {
// => Event type guard condition
// => Chained method calls or nested operations
// => Conditional check
// => Branch execution based on condition
this.state = "LoggedIn"; // => LoggedOut → LoggedIn
this.currentUser = event.username; // => Store username from payload
this.failedAttempts = 0; // => Reset counter
} else {
// => Statement execution
// => Fallback branch
this.failedAttempts += 1; // => Increment failures
if (this.failedAttempts >= 3) {
// => Conditional branch
// => Comparison check
// => Comparison check
// => Conditional check
// => Branch execution based on condition
this.state = "Locked"; // => LoggedOut → Locked (3 failures)
}
}
} else if (event.type === "logout" && this.state === "LoggedIn") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "LoggedOut"; // => LoggedIn → LoggedOut
this.currentUser = null; // => Clear user
}
}
private authenticateUser(username: string, password: string): boolean {
// => Method invocation
// => Extended state (data beyond FSM state)
return username === "admin" && password === "secret"; // => Mock authentication
// => FSM state management logic
}
}
// Usage
const auth = new AuthSystem(); // => state: "LoggedOut", user: null
auth.handleEvent({
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
// => Login attempt with payload
type: "login", // => Statement execution
username: "admin", // => Statement execution
password: "secret", // => Statement execution
}); // => Statement execution
console.log(auth.getCurrentState()); // => Output: LoggedIn
// => FSM state management logic
// => Pure read, no side effects
console.log(auth.getCurrentUser()); // => Output: admin
// => FSM state management logic
auth.handleEvent({ type: "logout" }); // => Logout
// => FSM state management logic
// => Processes events, triggers transitions
console.log(auth.getCurrentState()); // => Output: LoggedOut
// => FSM state management logic
// => Pure read, no side effects
console.log(auth.getCurrentUser()); // => Output: null
// => FSM state management logic
Key Takeaway: Events carry data (payloads) used during transitions. Login event includes username/password; logout has no payload. Payloads enable data-driven state changes.
Why It Matters: Event payloads decouple event sources from state machines. When Slack implemented presence status FSM, events carried context (user_id, timestamp, device_type) enabling rich state transitions without tight coupling to UI components. Event payload standardization reduced integration bugs by 70% because every event consumer received consistent data structure.
Example 10: Timed Transitions
Some transitions occur automatically after time delay, without explicit external events.
TypeScript Implementation:
// Session timeout FSM with timed transitions
type SessionState = "Active" | "Idle" | "TimedOut"; // => Three states
// => Type system ensures only valid states used
class SessionManager {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: SessionState = "Active"; // => Initial: Active
// => FSM begins execution in Active state
private lastActivity: Date = new Date(); // => Track last activity time
// => Initialized alongside FSM state
getCurrentState(): SessionState {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns state
}
recordActivity(): void {
// => Method invocation
// => Begin object/config definition
this.lastActivity = new Date(); // => Update activity timestamp
// => FSM state management logic
if (this.state === "Idle") {
// => State-based guard condition
// => Comparison check
// => Comparison check
// => Guard condition: check current state is Idle
// => Only execute if condition true
this.state = "Active"; // => Idle → Active (activity detected)
}
}
checkTimeout(): void {
// => Method invocation
// => Begin object/config definition
const now = new Date(); // => Current time
const inactiveMs = now.getTime() - this.lastActivity.getTime(); // => Milliseconds inactive
// => FSM state management logic
const idleThreshold = 5 * 60 * 1000; // => 5 minutes in ms
const timeoutThreshold = 30 * 60 * 1000; // => 30 minutes in ms
if (this.state === "Active" && inactiveMs > idleThreshold) {
// => State-based guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Conditional check
// => Branch execution based on condition
this.state = "Idle"; // => Active → Idle (5min inactive)
console.log("Session idle after 5 minutes"); // => Output for verification
// => Debug/audit output
// => Log for observability
} else if (this.state === "Idle" && inactiveMs > timeoutThreshold) {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "TimedOut"; // => Idle → TimedOut (30min inactive)
console.log("Session timed out after 30 minutes"); // => Output for verification
// => Debug/audit output
// => Log for observability
}
// => Timed transitions checked by periodic timer
}
}
// Usage (simulated with manual time checks)
const session = new SessionManager(); // => state: "Active"
console.log(session.getCurrentState()); // => Output: Active
// => FSM state management logic
// => Pure read, no side effects
// Simulate 5 minutes passing
session.checkTimeout(); // => Would check: Active → Idle?
// => FSM state management logic
session.recordActivity(); // => Reset activity timer
console.log(session.getCurrentState()); // => Output: Active (activity prevents timeout)
// => FSM state management logic
// => Pure read, no side effects
Key Takeaway: Timed transitions occur based on elapsed time, checked periodically or via timers. SessionManager tracks inactivity duration and transitions through states (Active → Idle → TimedOut) automatically.
Why It Matters: Timed transitions model real-world constraints. When banking apps implement session security, FSMs with timed transitions (Active → Warning@4min → TimedOut@5min) balance security and usability. Explicit timeout states enable graceful warnings (“Session expiring in 1 minute”) instead of abrupt logouts. Chase Bank’s FSM-based session management reduced customer support calls about unexpected timeouts by 60%.
Example 11: Event Prioritization
When multiple events arrive simultaneously, FSMs need event prioritization rules.
TypeScript Implementation:
// Emergency system FSM with event prioritization
type SystemState = "Normal" | "Warning" | "Critical" | "Emergency"; // => Four states
// => Type system ensures only valid states used
interface SystemEvent {
// => Type declaration defines structure
// => Begin object/config definition
type: "minor_issue" | "major_issue" | "critical_failure" | "emergency"; // => Event types
priority: number; // => Priority: 1 (low) to 4 (high)
// => FSM state management logic
}
class EmergencySystem {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: SystemState = "Normal"; // => Initial: Normal
// => FSM begins execution in Normal state
private eventQueue: SystemEvent[] = []; // => Queue for events
// => Initialized alongside FSM state
getCurrentState(): SystemState {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns state
}
enqueueEvent(event: SystemEvent): void {
// => Method invocation
// => Begin object/config definition
this.eventQueue.push(event); // => Add event to queue
this.eventQueue.sort((a, b) => b.priority - a.priority); // => Sort by priority (high first)
// => FSM state management logic
}
processNextEvent(): void {
// => Method invocation
// => Begin object/config definition
if (this.eventQueue.length === 0) return; // => No events to process
// => Branch execution based on condition
const event = this.eventQueue.shift()!; // => Get highest priority event
this.handleEvent(event); // => Process event
// => Processes events, triggers transitions
}
private handleEvent(event: SystemEvent): void {
// => Event handler method
// => Extended state (data beyond FSM state)
if (event.type === "minor_issue") {
// => Event type guard condition
// => Comparison check
// => Comparison check
// => Conditional check
// => Branch execution based on condition
if (this.state === "Normal") {
// => State-based guard condition
// => Comparison check
// => Comparison check
// => Guard condition: check current state is Normal
// => Only execute if condition true
this.state = "Warning"; // => Normal → Warning
}
} else if (event.type === "major_issue") {
// => Method signature: defines function interface
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "Critical"; // => Any → Critical
} else if (event.type === "emergency") {
// => Method signature: defines function interface
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "Emergency"; // => Any → Emergency (highest priority)
}
}
}
// Usage
const system = new EmergencySystem(); // => state: "Normal", queue: []
system.enqueueEvent({ type: "minor_issue", priority: 1 }); // => Add low-priority event
// => FSM state management logic
system.enqueueEvent({ type: "emergency", priority: 4 }); // => Add high-priority event
// => FSM state management logic
system.enqueueEvent({ type: "major_issue", priority: 3 }); // => Add medium-priority event
// => FSM state management logic
// => Queue after sorting: [emergency(4), major_issue(3), minor_issue(1)]
system.processNextEvent(); // => Process emergency (priority 4)
// => FSM state management logic
console.log(system.getCurrentState()); // => Output: Emergency (not Warning or Critical)
// => FSM state management logic
// => Pure read, no side effects
Key Takeaway: Event prioritization ensures critical events process before less important ones. Queue sorts events by priority; FSM processes highest-priority events first.
Why It Matters: Priority matters in safety-critical systems. When Boeing 737 MAX analyzed their flight control FSM, they found that sensor malfunction events (low priority) were processing before pilot override events (high priority), contributing to MCAS disasters. Event prioritization (pilot commands always preempt automated systems) is now mandatory in aviation software. Priority queues prevent low-priority events from blocking critical operations.
Example 12: Event History and Replay
FSMs can record event history for debugging, auditing, or replay.
TypeScript Implementation:
// Order processing FSM with event history
type OrderState = "Pending" | "Processing" | "Shipped" | "Delivered"; // => Four states
// => Type system ensures only valid states used
type OrderEvent = "start_processing" | "ship" | "deliver"; // => Three events
// => Events trigger state transitions
interface HistoryEntry {
// => Type declaration defines structure
// => Begin object/config definition
timestamp: Date; // => When event occurred
event: OrderEvent; // => What event happened
previousState: OrderState; // => State before transition
newState: OrderState; // => State after transition
}
class OrderWithHistory {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: OrderState = "Pending"; // => Initial: Pending
// => FSM begins execution in Pending state
private history: HistoryEntry[] = []; // => Event history log
// => Initialized alongside FSM state
getCurrentState(): OrderState {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns state
}
getHistory(): HistoryEntry[] {
// => Accessor method definition
// => Begin object/config definition
return this.history; // => Returns full history
}
handleEvent(event: OrderEvent): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
const previousState = this.state; // => Store state before transition
if (this.state === "Pending" && event === "start_processing") {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Event type check
// => Combined (state, event) guard
this.state = "Processing"; // => Pending → Processing
} else if (this.state === "Processing" && event === "ship") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "Shipped"; // => Processing → Shipped
} else if (this.state === "Shipped" && event === "deliver") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "Delivered"; // => Shipped → Delivered
}
if (previousState !== this.state) {
// => State-based guard condition
// => Comparison check
// => Comparison check
// => Conditional check
// => Branch execution based on condition
// => Transition occurred
this.history.push({
// => Statement execution
// => Add to collection
// => Add to collection
// => Begin object/config definition
// => Record in history
timestamp: new Date(), // => Method invocation
event, // => Statement execution
previousState, // => Statement execution
newState: this.state, // => Statement execution
// => Access instance property
// => Access instance property
}); // => Statement execution
}
}
}
// Usage
const order = new OrderWithHistory(); // => state: "Pending", history: []
order.handleEvent("start_processing"); // => Pending → Processing
// => Processes events, triggers transitions
order.handleEvent("ship"); // => Processing → Shipped
// => Processes events, triggers transitions
order.handleEvent("deliver"); // => Shipped → Delivered
// => Processes events, triggers transitions
console.log("Current state:", order.getCurrentState()); // => Output: Delivered
// => FSM state management logic
// => Pure read, no side effects
order.getHistory().forEach((entry, i) => {
// => Method invocation
// => Chained method calls or nested operations
// => Begin object/config definition
// => Print history
console.log(`${i + 1}. ${entry.previousState} → ${entry.newState} (${entry.event})`); // => Output for verification
// => Chained method calls or nested operations
// => Debug/audit output
// => Log for observability
}); // => Statement execution
// => Output:
// 1. Pending → Processing (start_processing)
// 2. Processing → Shipped (ship)
// 3. Shipped → Delivered (deliver)
Key Takeaway: Event history records all state transitions with timestamps and triggering events. Enables auditing, debugging, and replay. History is append-only log of state changes.
Why It Matters: Audit trails are regulatory requirements for financial and healthcare systems. Transaction FSMs with event history reduce fraud investigation time because complete state transition history is available. Event replay (reprocess same events on fresh FSM) becomes critical debugging tool—production bugs are diagnosed via local event replay from production logs.
Example 13: Conditional Events (Event Guards)
Event guards determine whether event is allowed based on conditions beyond current state.
TypeScript Implementation:
// Inventory FSM with stock-based event guards
type InventoryState = "Available" | "LowStock" | "OutOfStock"; // => Three states
// => Type system ensures only valid states used
interface PurchaseEvent {
// => Type declaration defines structure
// => Begin object/config definition
type: "purchase"; // => Event type
quantity: number; // => How many units
}
interface RestockEvent {
// => Type declaration defines structure
// => Begin object/config definition
type: "restock"; // => Event type
quantity: number; // => How many units added
}
type InventoryEvent = PurchaseEvent | RestockEvent; // => Type declaration defines structure
// => Assign value
// => Assign value
// => Defines event alphabet for FSM
// => Events trigger state transitions
class Inventory {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: InventoryState = "Available"; // => Initial: Available
// => FSM begins execution in Available state
private stock = 100; // => Current stock level
// => Initialized alongside FSM state
private lowStockThreshold = 20; // => Threshold for LowStock state
// => Initialized alongside FSM state
getCurrentState(): InventoryState {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns state
}
getStock(): number {
// => Accessor method definition
// => Begin object/config definition
return this.stock; // => Returns stock level
}
handleEvent(event: InventoryEvent): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (event.type === "purchase") {
// => Event type guard condition
// => Comparison check
// => Comparison check
// => Conditional check
// => Branch execution based on condition
if (this.stock >= event.quantity) {
// => Event type guard condition
// => Comparison check
// => Comparison check
// => Conditional check
// => Branch execution based on condition
// => Guard: sufficient stock?
this.stock -= event.quantity; // => Deduct stock
this.updateState(); // => Check state transition
} else {
// => Statement execution
// => Fallback branch
throw new Error(`Insufficient stock. Available: ${this.stock}, Requested: ${event.quantity}`); // => Method invocation
// => Constructor creates new object instance
// => Reject invalid operation
// => Fail fast on FSM violation
}
} else if (event.type === "restock") {
// => Method signature: defines function interface
// => Comparison check
// => Comparison check
// => Alternative condition
this.stock += event.quantity; // => Add stock
this.updateState(); // => Check state transition
}
}
private updateState(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
if (this.stock === 0) {
// => Conditional branch
// => Comparison check
// => Comparison check
// => Conditional check
// => Branch execution based on condition
this.state = "OutOfStock"; // => → OutOfStock (guard: stock == 0)
} else if (this.stock <= this.lowStockThreshold) {
// => Method signature: defines function interface
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "LowStock"; // => → LowStock (guard: stock <= 20)
} else {
// => Statement execution
// => Fallback branch
this.state = "Available"; // => → Available (guard: stock > 20)
}
// => State determined by stock level guards
}
}
// Usage
const inventory = new Inventory(); // => state: "Available", stock: 100
inventory.handleEvent({ type: "purchase", quantity: 85 }); // => stock: 15
// => FSM state management logic
// => Processes events, triggers transitions
console.log(inventory.getCurrentState()); // => Output: LowStock (15 <= 20)
// => FSM state management logic
// => Pure read, no side effects
inventory.handleEvent({ type: "purchase", quantity: 15 }); // => stock: 0
// => FSM state management logic
// => Processes events, triggers transitions
console.log(inventory.getCurrentState()); // => Output: OutOfStock (0 == 0)
// => FSM state management logic
// => Pure read, no side effects
try {
// => Statement execution
// => Begin error handling
// => Begin error handling
// => Begin object/config definition
inventory.handleEvent({ type: "purchase", quantity: 1 }); // => Guard fails (stock < quantity)
// => FSM state management logic
// => Processes events, triggers transitions
} catch (error) {
// => Method signature: defines function interface
// => Catch errors
// => Catch errors
// => Begin object/config definition
console.log(error.message); // => Output: Insufficient stock. Available: 0, Requested: 1
}
inventory.handleEvent({ type: "restock", quantity: 50 }); // => stock: 50
// => FSM state management logic
// => Processes events, triggers transitions
console.log(inventory.getCurrentState()); // => Output: Available (50 > 20)
// => FSM state management logic
// => Pure read, no side effects
Key Takeaway: Event guards (conditions) determine whether events are allowed. Guards check data beyond state (stock levels, permissions, quotas). Failed guards prevent transitions and may throw errors.
Why It Matters: Guards enforce business rules. Shopping cart FSMs use guards to prevent “checkout” event unless cart total exceeds minimum thresholds. Guard-based validation reduces customer support contacts about failed checkouts because errors are clear (“Add more for checkout”) instead of cryptic. Guards centralize validation logic that would otherwise scatter across UI components.
Guards and Conditions (Examples 14-18)
Example 14: Permission-Based Guards
Guards can check user permissions before allowing state transitions.
TypeScript Implementation:
// Document approval FSM with role-based guards
type DocApprovalState = "Draft" | "Review" | "Approved" | "Published"; // => Four states
// => Type system ensures only valid states used
type UserRole = "Author" | "Reviewer" | "Publisher"; // => Three roles
interface SubmitEvent {
// => Type declaration defines structure
// => Begin object/config definition
type: "submit"; // => Event type
user: UserRole; // => Who triggered event
}
interface ApproveEvent {
// => Type declaration defines structure
// => Begin object/config definition
type: "approve"; // => Event type
user: UserRole; // => Who triggered event
}
interface PublishEvent {
// => Type declaration defines structure
// => Begin object/config definition
type: "publish"; // => Event type
user: UserRole; // => Who triggered event
}
type DocEvent = SubmitEvent | ApproveEvent | PublishEvent; // => Type declaration defines structure
// => Assign value
// => Assign value
// => Defines event alphabet for FSM
// => Events trigger state transitions
class DocumentApproval {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: DocApprovalState = "Draft"; // => Initial: Draft
// => FSM begins execution in Draft state
getCurrentState(): DocApprovalState {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns state
}
handleEvent(event: DocEvent): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (event.type === "submit" && this.state === "Draft") {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Conditional check
// => Branch execution based on condition
if (event.user === "Author") {
// => Event type guard condition
// => Comparison check
// => Comparison check
// => Conditional check
// => Branch execution based on condition
// => Guard: only Author can submit
this.state = "Review"; // => Draft → Review
} else {
// => Statement execution
// => Fallback branch
throw new Error("Only Authors can submit drafts"); // => Method invocation
// => Reject invalid operation
// => Fail fast on FSM violation
}
} else if (event.type === "approve" && this.state === "Review") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
if (event.user === "Reviewer") {
// => Event type guard condition
// => Comparison check
// => Comparison check
// => Conditional check
// => Branch execution based on condition
// => Guard: only Reviewer can approve
this.state = "Approved"; // => Review → Approved
} else {
// => Statement execution
// => Fallback branch
throw new Error("Only Reviewers can approve documents"); // => Method invocation
// => Reject invalid operation
// => Fail fast on FSM violation
}
} else if (event.type === "publish" && this.state === "Approved") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
if (event.user === "Publisher") {
// => Event type guard condition
// => Comparison check
// => Comparison check
// => Conditional check
// => Branch execution based on condition
// => Guard: only Publisher can publish
this.state = "Published"; // => Approved → Published
} else {
// => Statement execution
// => Fallback branch
throw new Error("Only Publishers can publish documents"); // => Method invocation
// => Reject invalid operation
// => Fail fast on FSM violation
}
}
}
}
// Usage
const doc = new DocumentApproval(); // => state: "Draft"
doc.handleEvent({ type: "submit", user: "Author" }); // => Draft → Review (guard passes)
// => FSM state management logic
// => Processes events, triggers transitions
console.log(doc.getCurrentState()); // => Output: Review
// => FSM state management logic
// => Pure read, no side effects
try {
// => Statement execution
// => Begin error handling
// => Begin error handling
// => Begin object/config definition
doc.handleEvent({ type: "approve", user: "Author" }); // => Guard fails (not Reviewer)
// => FSM state management logic
// => Processes events, triggers transitions
} catch (error) {
// => Method signature: defines function interface
// => Catch errors
// => Catch errors
// => Begin object/config definition
console.log(error.message); // => Output: Only Reviewers can approve documents
}
doc.handleEvent({ type: "approve", user: "Reviewer" }); // => Review → Approved (guard passes)
// => FSM state management logic
// => Processes events, triggers transitions
console.log(doc.getCurrentState()); // => Output: Approved
// => FSM state management logic
// => Pure read, no side effects
Key Takeaway: Permission guards enforce role-based access control. Events carry user information; guards validate user has required role for transition. Unauthorized transitions throw errors.
Why It Matters: Permission guards centralize authorization logic. PR approval FSMs use permission guards (only maintainers can merge) to prevent unauthorized merge attempts at state machine level instead of relying on UI button visibility. Defense-in-depth: UI hides buttons, but FSM guards prevent API abuse or UI bugs from bypassing security.
Example 15: Time-Based Guards
Guards can restrict transitions based on time constraints (business hours, embargo dates, rate limits).
TypeScript Implementation:
// Trading system FSM with time-based guards
type TradingState = "MarketClosed" | "PreMarket" | "MarketOpen" | "AfterHours"; // => Four states
// => Type system ensures only valid states used
class TradingSystem {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: TradingState = "MarketClosed"; // => Initial: MarketClosed
// => FSM begins execution in MarketClosed state
getCurrentState(): TradingState {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns state
}
updateState(): void {
// => Method invocation
// => Begin object/config definition
const now = new Date(); // => Current time
const hour = now.getHours(); // => Current hour (0-23)
// => FSM state management logic
const day = now.getDay(); // => Day of week (0=Sunday, 6=Saturday)
// => FSM state management logic
const isWeekday = day >= 1 && day <= 5; // => Guard: Monday-Friday
// => FSM state management logic
const isPreMarketHour = hour >= 4 && hour < 9; // => Guard: 4am-9am
// => FSM state management logic
const isMarketHour = hour >= 9 && hour < 16; // => Guard: 9am-4pm
// => FSM state management logic
const isAfterHoursTime = hour >= 16 && hour < 20; // => Guard: 4pm-8pm
// => FSM state management logic
if (!isWeekday) {
// => Conditional branch
// => Conditional check
// => Branch execution based on condition
this.state = "MarketClosed"; // => Weekend → MarketClosed
} else if (isPreMarketHour) {
// => Method signature: defines function interface
// => Alternative condition
this.state = "PreMarket"; // => Weekday 4am-9am → PreMarket
} else if (isMarketHour) {
// => Method signature: defines function interface
// => Alternative condition
this.state = "MarketOpen"; // => Weekday 9am-4pm → MarketOpen
} else if (isAfterHoursTime) {
// => Method signature: defines function interface
// => Alternative condition
this.state = "AfterHours"; // => Weekday 4pm-8pm → AfterHours
} else {
// => Statement execution
// => Fallback branch
this.state = "MarketClosed"; // => Other times → MarketClosed
}
// => Time-based guards determine state
}
canPlaceTrade(): boolean {
// => Method invocation
// => Begin object/config definition
return this.state === "MarketOpen" || this.state === "AfterHours"; // => Guard: trading allowed?
// => FSM state management logic
}
}
// Usage
const trading = new TradingSystem(); // => state: "MarketClosed"
trading.updateState(); // => Check current time, update state
console.log(trading.getCurrentState()); // => Output: depends on current time
// => FSM state management logic
// => Pure read, no side effects
console.log(trading.canPlaceTrade()); // => Output: true if MarketOpen/AfterHours
// => FSM state management logic
Key Takeaway: Time-based guards restrict operations to valid time windows. Trading system only allows trades during market hours. Guards check current time and enforce business rules (no weekend trading).
Why It Matters: Time constraints prevent invalid operations. When Robinhood’s trading system failed to enforce time guards during extended outages, users placed orders at invalid times, creating 15,000 failed trades requiring manual reconciliation. Time-based FSM guards (reject trades outside market hours) would have prevented this—failed fast with clear error instead of accepting and failing later.
Example 16: Resource Availability Guards
Guards can check external resource availability (database connections, API quotas, memory).
TypeScript Implementation:
// Job scheduler FSM with resource guards
type JobState = "Queued" | "Running" | "Completed" | "Failed"; // => Four states
// => Type system ensures only valid states used
interface Job {
// => Type declaration defines structure
// => Begin object/config definition
id: string; // => Job identifier
memoryRequired: number; // => Memory in MB
}
class JobScheduler {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: JobState = "Queued"; // => Initial: Queued
// => FSM begins execution in Queued state
private availableMemory = 1024; // => 1GB available
// => Initialized alongside FSM state
private currentJob: Job | null = null; // => Currently running job
// => Initialized alongside FSM state
getCurrentState(): JobState {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns state
}
startJob(job: Job): void {
// => Method invocation
// => Begin object/config definition
if (this.state !== "Queued") {
// => State-based guard condition
// => Comparison check
// => Comparison check
// => Conditional check
// => Branch execution based on condition
throw new Error("Can only start jobs in Queued state"); // => Method invocation
// => Reject invalid operation
// => Fail fast on FSM violation
}
if (job.memoryRequired > this.availableMemory) {
// => Conditional branch
// => Conditional check
// => Branch execution based on condition
// => Guard: sufficient memory?
this.state = "Failed"; // => Queued → Failed (insufficient resources)
throw new Error(`Insufficient memory. Required: ${job.memoryRequired}MB, Available: ${this.availableMemory}MB`); // => Method invocation
// => Constructor creates new object instance
// => Reject invalid operation
// => Fail fast on FSM violation
}
this.availableMemory -= job.memoryRequired; // => Allocate memory
this.currentJob = job; // => Assign job
this.state = "Running"; // => Queued → Running (guard passed)
console.log(`Job ${job.id} started, ${this.availableMemory}MB remaining`); // => Output for verification
// => Debug/audit output
// => Log for observability
}
completeJob(): void {
// => Method invocation
// => Begin object/config definition
if (this.state !== "Running") {
// => State-based guard condition
// => Comparison check
// => Comparison check
// => Conditional check
// => Branch execution based on condition
throw new Error("No job running"); // => Method invocation
// => Reject invalid operation
// => Fail fast on FSM violation
}
this.availableMemory += this.currentJob!.memoryRequired; // => Release memory
this.state = "Completed"; // => Running → Completed
console.log(`Job ${this.currentJob!.id} completed, ${this.availableMemory}MB available`); // => Output for verification
// => Debug/audit output
// => Log for observability
}
}
// Usage
const scheduler = new JobScheduler(); // => state: "Queued", memory: 1024MB
scheduler.startJob({ id: "job1", memoryRequired: 512 }); // => Queued → Running (512MB allocated)
// => FSM state management logic
console.log(scheduler.getCurrentState()); // => Output: Running
// => FSM state management logic
// => Pure read, no side effects
scheduler.completeJob(); // => Running → Completed (512MB released)
// => FSM state management logic
console.log(scheduler.getCurrentState()); // => Output: Completed
// => FSM state management logic
// => Pure read, no side effects
const scheduler2 = new JobScheduler(); // => New scheduler
try {
// => Statement execution
// => Begin error handling
// => Begin error handling
// => Begin object/config definition
scheduler2.startJob({ id: "job2", memoryRequired: 2048 }); // => Guard fails (2048MB > 1024MB)
// => FSM state management logic
} catch (error) {
// => Method signature: defines function interface
// => Catch errors
// => Catch errors
// => Begin object/config definition
console.log(error.message); // => Output: Insufficient memory. Required: 2048MB, Available: 1024MB
console.log(scheduler2.getCurrentState()); // => Output: Failed
// => FSM state management logic
// => Pure read, no side effects
}Key Takeaway: Resource guards check availability (memory, connections, quotas) before transitions. Failed resource checks prevent transitions and may move to error states.
Why It Matters: Resource guards prevent cascading failures. When Kubernetes schedules pods, FSM guards check node resources (CPU, memory, disk) before transitioning pod to Running state. Failed guards keep pod in Pending state instead of starting and immediately crashing. Resource-aware FSMs reduce cluster instability by 80% compared to optimistic scheduling.
Example 17: Data Validation Guards
Guards validate data integrity before allowing transitions.
TypeScript Implementation:
// Payment processing FSM with validation guards
type PaymentState = "Pending" | "Validated" | "Processed" | "Rejected"; // => Four states
// => Type system ensures only valid states used
interface PaymentData {
// => Type declaration defines structure
// => Begin object/config definition
amount: number; // => Payment amount
cardNumber: string; // => Card number
cvv: string; // => CVV code
}
class PaymentProcessor {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: PaymentState = "Pending"; // => Initial: Pending
// => FSM begins execution in Pending state
private paymentData: PaymentData | null = null; // => Payment details
// => Initialized alongside FSM state
getCurrentState(): PaymentState {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns state
}
validatePayment(data: PaymentData): void {
// => Method invocation
// => Begin object/config definition
if (this.state !== "Pending") {
// => State-based guard condition
// => Comparison check
// => Comparison check
// => Conditional check
// => Branch execution based on condition
throw new Error("Can only validate Pending payments"); // => Method invocation
// => Reject invalid operation
// => Fail fast on FSM violation
}
this.paymentData = data; // => Store payment data
// Guard: amount validation
if (data.amount <= 0) {
// => Conditional branch
// => Comparison check
// => Comparison check
// => Conditional check
// => Branch execution based on condition
// => Guard: positive amount?
this.state = "Rejected"; // => Pending → Rejected (invalid amount)
throw new Error("Amount must be positive"); // => Method invocation
// => Reject invalid operation
// => Fail fast on FSM violation
}
// Guard: card number validation (simple check)
if (data.cardNumber.length !== 16) {
// => Conditional branch
// => Comparison check
// => Comparison check
// => Conditional check
// => Branch execution based on condition
// => Guard: 16-digit card?
this.state = "Rejected"; // => Pending → Rejected (invalid card)
throw new Error("Invalid card number length"); // => Method invocation
// => Reject invalid operation
// => Fail fast on FSM violation
}
// Guard: CVV validation
if (data.cvv.length !== 3 && data.cvv.length !== 4) {
// => Conditional branch
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Conditional check
// => Branch execution based on condition
// => Guard: valid CVV?
this.state = "Rejected"; // => Pending → Rejected (invalid CVV)
throw new Error("Invalid CVV length"); // => Method invocation
// => Reject invalid operation
// => Fail fast on FSM violation
}
this.state = "Validated"; // => Pending → Validated (all guards passed)
console.log("Payment validated successfully"); // => Output for verification
// => Debug/audit output
// => Log for observability
}
processPayment(): void {
// => Method invocation
// => Begin object/config definition
if (this.state !== "Validated") {
// => State-based guard condition
// => Comparison check
// => Comparison check
// => Conditional check
// => Branch execution based on condition
throw new Error("Can only process Validated payments"); // => Method invocation
// => Reject invalid operation
// => Fail fast on FSM violation
}
this.state = "Processed"; // => Validated → Processed
console.log(`Processed payment of $${this.paymentData!.amount}`); // => Output for verification
// => Debug/audit output
// => Log for observability
}
}
// Usage
const payment1 = new PaymentProcessor(); // => state: "Pending"
payment1.validatePayment({
// => Statement execution
// => Begin object/config definition
// => All guards pass
amount: 99.99, // => Statement execution
cardNumber: "1234567890123456", // => Statement execution
cvv: "123", // => Statement execution
}); // => Statement execution
console.log(payment1.getCurrentState()); // => Output: Validated
// => FSM state management logic
// => Pure read, no side effects
payment1.processPayment(); // => Validated → Processed
console.log(payment1.getCurrentState()); // => Output: Processed
// => FSM state management logic
// => Pure read, no side effects
const payment2 = new PaymentProcessor(); // => state: "Pending"
try {
// => Statement execution
// => Begin error handling
// => Begin error handling
// => Begin object/config definition
payment2.validatePayment({
// => Statement execution
// => Begin object/config definition
// => Guard fails (invalid amount)
amount: -10, // => Statement execution
cardNumber: "1234567890123456", // => Statement execution
cvv: "123", // => Statement execution
}); // => Statement execution
} catch (error) {
// => Method signature: defines function interface
// => Catch errors
// => Catch errors
// => Begin object/config definition
console.log(error.message); // => Output: Amount must be positive
console.log(payment2.getCurrentState()); // => Output: Rejected
// => FSM state management logic
// => Pure read, no side effects
}Key Takeaway: Data validation guards check input data meets requirements (range checks, format validation, business rules). Multiple guards may run sequentially; first failure rejects transition.
Why It Matters: Validation guards prevent garbage-in-garbage-out. Payment systems experience failures from processing invalid data that should have been rejected earlier. Moving validation into FSM guards (before Pending → Validated transition) reduces processing costs and improves error messages because validation failures happen synchronously (immediate user feedback) instead of asynchronously (delayed notification).
Example 18: Composite Guards (AND/OR Logic)
Complex guards combine multiple conditions using boolean logic.
TypeScript Implementation:
// Loan approval FSM with composite guards
type LoanState = "Application" | "UnderReview" | "Approved" | "Rejected"; // => Four states
// => Type system ensures only valid states used
interface ApplicantData {
// => Type declaration defines structure
// => Begin object/config definition
creditScore: number; // => Credit score (300-850)
income: number; // => Annual income
loanAmount: number; // => Requested loan amount
hasCollateral: boolean; // => Has collateral?
// => FSM state management logic
}
class LoanApproval {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: LoanState = "Application"; // => Initial: Application
// => FSM begins execution in Application state
private applicantData: ApplicantData | null = null; // => Applicant data
// => Initialized alongside FSM state
getCurrentState(): LoanState {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns state
}
submitApplication(data: ApplicantData): void {
// => Method invocation
// => Begin object/config definition
if (this.state !== "Application") {
// => State-based guard condition
// => Comparison check
// => Comparison check
// => Conditional check
// => Branch execution based on condition
throw new Error("Invalid state for submission"); // => Method invocation
// => Reject invalid operation
// => Fail fast on FSM violation
}
this.applicantData = data; // => Store applicant data
this.state = "UnderReview"; // => Application → UnderReview
}
reviewApplication(): void {
// => Method invocation
// => Begin object/config definition
if (this.state !== "UnderReview" || !this.applicantData) {
// => State-based guard condition
// => Logical OR: either condition can be true
// => Comparison check
// => Comparison check
// => Conditional check
// => Branch execution based on condition
throw new Error("Invalid state for review"); // => Method invocation
// => Reject invalid operation
// => Fail fast on FSM violation
}
const data = this.applicantData; // => Get applicant data
// Composite guard using AND/OR logic
const hasGoodCredit = data.creditScore >= 700; // => Guard: credit >= 700
const hasStableIncome = data.income >= 50000; // => Guard: income >= \$50k
const reasonableDebtRatio = data.loanAmount / data.income <= 3; // => Guard: loan <= 3x income
// Approval logic:
// (Good credit AND stable income AND reasonable debt ratio) OR has collateral
const approved = (hasGoodCredit && hasStableIncome && reasonableDebtRatio) || data.hasCollateral; // => Variable declaration and assignment
// => Logical AND: both conditions must be true
// => Assign value
// => Assign value
// => Initialize approved
// => Composite guard: (A AND B AND C) OR D
if (approved) {
// => Conditional branch
// => Conditional check
// => Branch execution based on condition
this.state = "Approved"; // => UnderReview → Approved
console.log("Loan approved"); // => Output for verification
// => Debug/audit output
// => Log for observability
} else {
// => Statement execution
// => Fallback branch
this.state = "Rejected"; // => UnderReview → Rejected
console.log("Loan rejected"); // => Output for verification
// => Debug/audit output
// => Log for observability
}
}
}
// Usage
const loan1 = new LoanApproval(); // => state: "Application"
loan1.submitApplication({
// => Statement execution
// => Begin object/config definition
// => All AND guards pass
creditScore: 750, // => Statement execution
income: 80000, // => Statement execution
loanAmount: 150000, // => Statement execution
hasCollateral: false, // => Statement execution
}); // => Statement execution
loan1.reviewApplication(); // => UnderReview → Approved
console.log(loan1.getCurrentState()); // => Output: Approved
// => FSM state management logic
// => Pure read, no side effects
const loan2 = new LoanApproval(); // => state: "Application"
loan2.submitApplication({
// => Statement execution
// => Begin object/config definition
// => AND guards fail, but OR passes
creditScore: 600, // => Statement execution
income: 40000, // => Statement execution
loanAmount: 200000, // => Statement execution
hasCollateral: true, // => Collateral enables approval
}); // => Statement execution
loan2.reviewApplication(); // => UnderReview → Approved (via collateral)
// => FSM state management logic
console.log(loan2.getCurrentState()); // => Output: Approved
// => FSM state management logic
// => Pure read, no side effects
const loan3 = new LoanApproval(); // => state: "Application"
loan3.submitApplication({
// => Statement execution
// => Begin object/config definition
// => Both AND and OR fail
creditScore: 600, // => Statement execution
income: 40000, // => Statement execution
loanAmount: 200000, // => Statement execution
hasCollateral: false, // => Statement execution
}); // => Statement execution
loan3.reviewApplication(); // => UnderReview → Rejected
console.log(loan3.getCurrentState()); // => Output: Rejected
// => FSM state management logic
// => Pure read, no side effects
Key Takeaway: Composite guards combine multiple conditions using AND/OR logic. Complex business rules (credit AND income AND debt ratio) OR (collateral) modeled as boolean expressions. All guard conditions must evaluate before transition.
Why It Matters: Real business rules are complex. When LendingClub implemented loan approval FSMs, composite guards captured actual underwriting rules (30+ conditions combined with AND/OR/NOT logic). Explicit guard logic reduced loan default rate by 15% because business rules were visible and testable. Analysts could review FSM guard logic directly instead of reverse-engineering from code.
Entry and Exit Actions (Examples 19-23)
Example 19: State Entry Actions
Entry actions execute when entering a state, regardless of which transition brought you there.
TypeScript Implementation:
// Game character FSM with entry actions
type CharacterState = "Idle" | "Walking" | "Running" | "Jumping"; // => Four states
// => Type system ensures only valid states used
type CharacterEvent = "walk" | "run" | "jump" | "land" | "stop"; // => Five events
// => Events trigger state transitions
class GameCharacter {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: CharacterState = "Idle"; // => Initial: Idle
// => FSM begins execution in Idle state
getCurrentState(): CharacterState {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns state
}
handleEvent(event: CharacterEvent): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
const previousState = this.state; // => Store previous state
// Transition logic
if (this.state === "Idle" && event === "walk") {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Event type check
// => Combined (state, event) guard
this.state = "Walking"; // => Idle → Walking
} else if (this.state === "Idle" && event === "run") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "Running"; // => Idle → Running
} else if (this.state === "Walking" && event === "run") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "Running"; // => Walking → Running
} else if (this.state === "Running" && event === "jump") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "Jumping"; // => Running → Jumping
} else if (this.state === "Jumping" && event === "land") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "Idle"; // => Jumping → Idle
} else if (event === "stop") {
// => Method signature: defines function interface
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "Idle"; // => Any → Idle (stop button)
}
// Entry actions (execute when entering new state)
if (this.state !== previousState) {
// => State-based guard condition
// => Comparison check
// => Comparison check
// => Conditional check
// => Branch execution based on condition
// => State changed?
this.onEnter(this.state); // => Execute entry action
}
}
private onEnter(state: CharacterState): void {
// => Method invocation
// => Extended state (data beyond FSM state)
if (state === "Walking") {
// => State-based guard condition
// => Comparison check
// => Comparison check
// => Conditional check
// => Branch execution based on condition
console.log("▶ Entry action: Start walking animation"); // => Entry action for Walking
// => Log for observability
this.setAnimationSpeed(1.0); // => Normal speed
} else if (state === "Running") {
// => Method signature: defines function interface
// => Comparison check
// => Comparison check
// => Alternative condition
console.log("▶ Entry action: Start running animation"); // => Entry action for Running
// => Log for observability
this.setAnimationSpeed(2.0); // => Double speed
} else if (state === "Jumping") {
// => Method signature: defines function interface
// => Comparison check
// => Comparison check
// => Alternative condition
console.log("▶ Entry action: Play jump sound"); // => Entry action for Jumping
// => Log for observability
this.playSound("jump.wav"); // => Trigger sound effect
} else if (state === "Idle") {
// => Method signature: defines function interface
// => Comparison check
// => Comparison check
// => Alternative condition
console.log("▶ Entry action: Start idle animation"); // => Entry action for Idle
// => Log for observability
this.setAnimationSpeed(0.5); // => Slow idle breathing
}
// => Entry actions run regardless of which transition brought us here
}
private setAnimationSpeed(speed: number): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(` Set animation speed: ${speed}x`); // => Mock animation API
// => FSM state management logic
// => Log for observability
}
private playSound(file: string): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(` Play sound: ${file}`); // => Mock audio API
// => FSM state management logic
// => Log for observability
}
}
// Usage
const character = new GameCharacter(); // => state: "Idle"
character.handleEvent("walk"); // => Idle → Walking
// => Processes events, triggers transitions
// => Output:
// ▶ Entry action: Start walking animation
// Set animation speed: 1.0x
character.handleEvent("run"); // => Walking → Running
// => Processes events, triggers transitions
// => Output:
// ▶ Entry action: Start running animation
// Set animation speed: 2.0x
character.handleEvent("jump"); // => Running → Jumping
// => Processes events, triggers transitions
// => Output:
// ▶ Entry action: Play jump sound
// Play sound: jump.wav
character.handleEvent("land"); // => Jumping → Idle
// => Processes events, triggers transitions
// => Output:
// ▶ Entry action: Start idle animation
// Set animation speed: 0.5x
Key Takeaway: Entry actions execute automatically when entering state, regardless of source state. Centralizes setup logic (start animations, play sounds) that must happen for every transition into that state.
Why It Matters: Entry actions eliminate code duplication. When Unity game developers analyzed character controller code, they found animation setup duplicated across 10+ transition handlers (Idle→Walking, Running→Walking, Jumping→Walking all called startWalkingAnimation()). Consolidating into Walking state entry action reduced code by 70% and fixed 15 bugs where some transitions forgot to call setup logic.
Example 20: State Exit Actions
Exit actions execute when leaving a state, regardless of which transition takes you out.
TypeScript Implementation:
// Database connection FSM with exit actions
type ConnectionState = "Disconnected" | "Connecting" | "Connected" | "Error"; // => Four states
// => Type system ensures only valid states used
type ConnectionEvent = "connect" | "connection_success" | "connection_failed" | "disconnect"; // => Four events
// => Events trigger state transitions
class DatabaseConnection {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: ConnectionState = "Disconnected"; // => Initial: Disconnected
// => FSM begins execution in Disconnected state
private connectionHandle: string | null = null; // => Mock connection handle
// => Initialized alongside FSM state
getCurrentState(): ConnectionState {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns state
}
handleEvent(event: ConnectionEvent): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
const previousState = this.state; // => Store previous state
// Transition logic
if (this.state === "Disconnected" && event === "connect") {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Event type check
// => Combined (state, event) guard
this.state = "Connecting"; // => Disconnected → Connecting
} else if (this.state === "Connecting" && event === "connection_success") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "Connected"; // => Connecting → Connected
} else if (this.state === "Connecting" && event === "connection_failed") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "Error"; // => Connecting → Error
} else if (this.state === "Connected" && event === "disconnect") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "Disconnected"; // => Connected → Disconnected
}
// Exit actions (execute when leaving state)
if (this.state !== previousState) {
// => State-based guard condition
// => Comparison check
// => Comparison check
// => Conditional check
// => Branch execution based on condition
// => State changed?
this.onExit(previousState); // => Execute exit action
this.onEnter(this.state); // => Then entry action
}
}
private onExit(state: ConnectionState): void {
// => Method invocation
// => Extended state (data beyond FSM state)
if (state === "Connected") {
// => State-based guard condition
// => Comparison check
// => Comparison check
// => Conditional check
// => Branch execution based on condition
console.log("◀ Exit action: Close database connection"); // => Exit action for Connected
// => Log for observability
this.closeConnection(); // => Cleanup resources
} else if (state === "Connecting") {
// => Method signature: defines function interface
// => Comparison check
// => Comparison check
// => Alternative condition
console.log("◀ Exit action: Cancel connection attempt"); // => Exit action for Connecting
// => Log for observability
this.cancelConnection(); // => Abort in-progress connection
}
// => Exit actions run regardless of which transition takes us out
}
private onEnter(state: ConnectionState): void {
// => Method invocation
// => Extended state (data beyond FSM state)
if (state === "Connecting") {
// => State-based guard condition
// => Comparison check
// => Comparison check
// => Conditional check
// => Branch execution based on condition
console.log("▶ Entry action: Open connection"); // => Entry action for Connecting
// => Log for observability
this.connectionHandle = "conn-" + Date.now(); // => Mock connection handle
} else if (state === "Connected") {
// => Method signature: defines function interface
// => Comparison check
// => Comparison check
// => Alternative condition
console.log("▶ Entry action: Connection established"); // => Entry action for Connected
// => Log for observability
}
}
private closeConnection(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(` Closing connection: ${this.connectionHandle}`); // => Mock close
// => FSM state management logic
// => Log for observability
this.connectionHandle = null; // => Release handle
}
private cancelConnection(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(` Cancelling connection: ${this.connectionHandle}`); // => Mock cancel
// => FSM state management logic
// => Log for observability
this.connectionHandle = null; // => Release handle
}
}
// Usage
const db = new DatabaseConnection(); // => state: "Disconnected"
db.handleEvent("connect"); // => Disconnected → Connecting
// => Processes events, triggers transitions
// => Output:
// ▶ Entry action: Open connection
// Opening connection: conn-1234567890
db.handleEvent("connection_success"); // => Connecting → Connected
// => Processes events, triggers transitions
// => Output:
// ◀ Exit action: Cancel connection attempt (even though success)
// Cancelling connection: conn-1234567890
// ▶ Entry action: Connection established
db.handleEvent("disconnect"); // => Connected → Disconnected
// => Processes events, triggers transitions
// => Output:
// ◀ Exit action: Close database connection
// Closing connection: conn-1234567890
Key Takeaway: Exit actions execute automatically when leaving state, before entering next state. Centralizes cleanup logic (close connections, release resources) that must happen for every transition out of that state.
Why It Matters: Exit actions prevent resource leaks. Desktop apps experience memory leaks when WebSocket connections aren’t closed when switching channels because each transition handler must remember to call cleanup methods. Moving cleanup to exit actions (leave ChannelA state → close socket) guarantees cleanup regardless of where you’re going, reducing memory leaks significantly.
Example 21: Entry and Exit Actions Together
Most FSMs use both entry and exit actions for complete state lifecycle management.
TypeScript Implementation:
// HTTP request FSM with entry/exit actions
type RequestState = "Idle" | "Pending" | "Success" | "Failed"; // => Four states
// => Type system ensures only valid states used
type RequestEvent = "send" | "response_ok" | "response_error" | "timeout"; // => Four events
// => Events trigger state transitions
class HttpRequest {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: RequestState = "Idle"; // => Initial: Idle
// => FSM begins execution in Idle state
private timeoutHandle: NodeJS.Timeout | null = null; // => Timeout timer
// => Initialized alongside FSM state
private requestStartTime: Date | null = null; // => Track request timing
// => Initialized alongside FSM state
getCurrentState(): RequestState {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns state
}
handleEvent(event: RequestEvent): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
const previousState = this.state; // => Store previous state
// Transition logic
if (this.state === "Idle" && event === "send") {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Event type check
// => Combined (state, event) guard
this.state = "Pending"; // => Idle → Pending
} else if (this.state === "Pending" && event === "response_ok") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "Success"; // => Pending → Success
} else if (this.state === "Pending" && event === "response_error") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "Failed"; // => Pending → Failed
} else if (this.state === "Pending" && event === "timeout") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "Failed"; // => Pending → Failed (timeout)
}
// Execute exit then entry actions
if (this.state !== previousState) {
// => State-based guard condition
// => Comparison check
// => Comparison check
// => Conditional check
// => Branch execution based on condition
this.onExit(previousState); // => Exit action first
this.onEnter(this.state); // => Entry action second
}
}
private onEnter(state: RequestState): void {
// => Method invocation
// => Extended state (data beyond FSM state)
if (state === "Pending") {
// => State-based guard condition
// => Comparison check
// => Comparison check
// => Conditional check
// => Branch execution based on condition
console.log("▶ Entry: Start request timer"); // => Entry action for Pending
// => Log for observability
this.requestStartTime = new Date(); // => Record start time
// => FSM state management logic
this.timeoutHandle = setTimeout(() => {
// => Event handler method
// => Chained method calls or nested operations
// => Assign value
// => Assign value
// => Begin object/config definition
// => Start timeout timer
this.handleEvent("timeout"); // => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
}, 5000); // => Statement execution
} else if (state === "Success") {
// => Method signature: defines function interface
// => Comparison check
// => Comparison check
// => Alternative condition
const duration = Date.now() - this.requestStartTime!.getTime(); // => Calculate duration
// => FSM state management logic
console.log(`▶ Entry: Request succeeded (${duration}ms)`); // => Entry action for Success
// => FSM state management logic
// => Log for observability
} else if (state === "Failed") {
// => Method signature: defines function interface
// => Comparison check
// => Comparison check
// => Alternative condition
console.log("▶ Entry: Request failed"); // => Entry action for Failed
// => Log for observability
}
}
private onExit(state: RequestState): void {
// => Method invocation
// => Extended state (data beyond FSM state)
if (state === "Pending") {
// => State-based guard condition
// => Comparison check
// => Comparison check
// => Conditional check
// => Branch execution based on condition
console.log("◀ Exit: Clear request timer"); // => Exit action for Pending
// => Log for observability
if (this.timeoutHandle) {
// => Event handler method
// => Conditional check
// => Branch execution based on condition
clearTimeout(this.timeoutHandle); // => Cancel timeout timer
this.timeoutHandle = null; // => Clear handle
}
}
// => Exit action ensures timer cleanup regardless of outcome
}
}
// Usage (simulated)
const request = new HttpRequest(); // => state: "Idle"
request.handleEvent("send"); // => Idle → Pending
// => Processes events, triggers transitions
// => Output:
// ▶ Entry: Start request timer
// Simulate successful response
request.handleEvent("response_ok"); // => Pending → Success
// => Processes events, triggers transitions
// => Output:
// ◀ Exit: Clear request timer
// ▶ Entry: Request succeeded (150ms)
Key Takeaway: Entry actions setup state resources (start timers, open connections); exit actions cleanup state resources (clear timers, close connections). Together they ensure complete state lifecycle management.
Why It Matters: Lifecycle management prevents bugs and resource leaks. Video conferencing systems experience connectivity issues from connection timers not being cleared when state changed, leading to spurious timeout errors. Entry/exit action pattern (entry: start timer, exit: clear timer) guarantees cleanup, significantly reducing false timeout errors.
Example 22: Entry/Exit Actions vs Transition Actions
Entry/exit actions run for ANY transition into/out of state. Transition actions run for SPECIFIC transitions only.
TypeScript Implementation (showing both):
// Authentication FSM comparing entry/exit vs transition actions
type AuthState = "LoggedOut" | "LoggingIn" | "LoggedIn"; // => Three states
// => Type system ensures only valid states used
type AuthEvent = "login" | "login_success" | "logout"; // => Three events
// => Events trigger state transitions
class AuthWithActions {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: AuthState = "LoggedOut"; // => Initial: LoggedOut
// => FSM begins execution in LoggedOut state
getCurrentState(): AuthState {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns state
}
handleEvent(event: AuthEvent): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
const previousState = this.state; // => Store previous state
// Transition logic with transition-specific actions
if (this.state === "LoggedOut" && event === "login") {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Event type check
// => Combined (state, event) guard
this.onTransition_LoggedOut_to_LoggingIn(); // => Transition action (specific)
// => FSM state management logic
this.state = "LoggingIn"; // => Change state
} else if (this.state === "LoggingIn" && event === "login_success") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "LoggedIn"; // => Change state
} else if (this.state === "LoggedIn" && event === "logout") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "LoggedOut"; // => Change state
}
// Execute exit/entry actions (run for ANY transition)
if (this.state !== previousState) {
// => State-based guard condition
// => Comparison check
// => Comparison check
// => Conditional check
// => Branch execution based on condition
this.onExit(previousState); // => Exit action (generic)
// => FSM state management logic
this.onEnter(this.state); // => Entry action (generic)
// => FSM state management logic
}
}
// ENTRY ACTIONS (run for ANY transition INTO this state)
private onEnter(state: AuthState): void {
// => Method invocation
// => Extended state (data beyond FSM state)
if (state === "LoggingIn") {
// => State-based guard condition
// => Comparison check
// => Comparison check
// => Conditional check
// => Branch execution based on condition
console.log("▶ Entry: Show loading spinner"); // => Runs for ANY transition into LoggingIn
// => Log for observability
} else if (state === "LoggedIn") {
// => Method signature: defines function interface
// => Comparison check
// => Comparison check
// => Alternative condition
console.log("▶ Entry: Load user dashboard"); // => Runs for ANY transition into LoggedIn
// => Log for observability
}
}
// EXIT ACTIONS (run for ANY transition OUT OF this state)
private onExit(state: AuthState): void {
// => Method invocation
// => Extended state (data beyond FSM state)
if (state === "LoggingIn") {
// => State-based guard condition
// => Comparison check
// => Comparison check
// => Conditional check
// => Branch execution based on condition
console.log("◀ Exit: Hide loading spinner"); // => Runs for ANY transition out of LoggingIn
// => Log for observability
}
}
// TRANSITION ACTIONS (run for SPECIFIC transition only)
private onTransition_LoggedOut_to_LoggingIn(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log("→ Transition: Validate credentials"); // => Only for LoggedOut → LoggingIn
// => Log for observability
console.log("→ Transition: Send API request"); // => Specific to this transition
// => Log for observability
}
}
// Usage
const auth = new AuthWithActions(); // => state: "LoggedOut"
auth.handleEvent("login"); // => LoggedOut → LoggingIn
// => Processes events, triggers transitions
// => Output:
// → Transition: Validate credentials (transition action)
// → Transition: Send API request (transition action)
// ▶ Entry: Show loading spinner (entry action)
auth.handleEvent("login_success"); // => LoggingIn → LoggedIn
// => Processes events, triggers transitions
// => Output:
// ◀ Exit: Hide loading spinner (exit action)
// ▶ Entry: Load user dashboard (entry action)
Key Takeaway: Entry/exit actions run for every transition into/out of state (generic setup/cleanup). Transition actions run for specific transitions only (specialized logic). Use entry/exit for common behavior, transition actions for unique behavior.
Why It Matters: Choosing correct action type prevents code duplication or scattered logic. When Twitter migrated to React, they analyzed state management and found 60% of component lifecycle methods (componentDidMount, componentWillUnmount) were duplicating logic that should be entry/exit actions. Proper separation (entry: start timer, exit: stop timer, transition: send specific analytics event) reduced React component code by 40%.
Example 23: Nested Entry/Exit Actions
Entry/exit actions can call other methods, enabling rich state lifecycle behaviors.
TypeScript Implementation:
// Video streaming FSM with nested entry/exit actions
type VideoState = "Stopped" | "Buffering" | "Playing" | "Paused"; // => Four states
// => Type system ensures only valid states used
type VideoEvent = "play" | "buffer_complete" | "pause" | "stop"; // => Four events
// => Events trigger state transitions
class VideoPlayer {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: VideoState = "Stopped"; // => Initial: Stopped
// => FSM begins execution in Stopped state
private bufferLevel = 0; // => Buffer percentage
// => Initialized alongside FSM state
getCurrentState(): VideoState {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns state
}
handleEvent(event: VideoEvent): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
const previousState = this.state; // => Store previous state
// Transition logic
if (event === "play" && this.state === "Stopped") {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Event type check
// => Combined (state, event) guard
this.state = "Buffering"; // => Stopped → Buffering
} else if (event === "buffer_complete" && this.state === "Buffering") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "Playing"; // => Buffering → Playing
} else if (event === "pause" && this.state === "Playing") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "Paused"; // => Playing → Paused
} else if (event === "play" && this.state === "Paused") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "Playing"; // => Paused → Playing
} else if (event === "stop") {
// => Method signature: defines function interface
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "Stopped"; // => Any → Stopped
}
// Execute lifecycle actions
if (this.state !== previousState) {
// => State-based guard condition
// => Comparison check
// => Comparison check
// => Conditional check
// => Branch execution based on condition
this.onExit(previousState); // => Exit previous state
this.onEnter(this.state); // => Enter new state
// => FSM state management logic
}
}
private onEnter(state: VideoState): void {
// => Method invocation
// => Extended state (data beyond FSM state)
if (state === "Buffering") {
// => State-based guard condition
// => Comparison check
// => Comparison check
// => Conditional check
// => Branch execution based on condition
console.log("▶ Entry: Start buffering"); // => Output for verification
// => Debug/audit output
// => Log for observability
this.startBuffering(); // => Nested call: start buffer
this.showBufferingIndicator(); // => Nested call: show UI
} else if (state === "Playing") {
// => Method signature: defines function interface
// => Comparison check
// => Comparison check
// => Alternative condition
console.log("▶ Entry: Start playback"); // => Output for verification
// => Debug/audit output
// => Log for observability
this.resumePlayback(); // => Nested call: play video
this.hideBufferingIndicator(); // => Nested call: hide UI
this.trackAnalytics("play"); // => Nested call: analytics
} else if (state === "Paused") {
// => Method signature: defines function interface
// => Comparison check
// => Comparison check
// => Alternative condition
console.log("▶ Entry: Pause playback"); // => Output for verification
// => Debug/audit output
// => Log for observability
this.pausePlayback(); // => Nested call: pause
} else if (state === "Stopped") {
// => Method signature: defines function interface
// => Comparison check
// => Comparison check
// => Alternative condition
console.log("▶ Entry: Stop playback"); // => Output for verification
// => Debug/audit output
// => Log for observability
this.stopPlayback(); // => Nested call: stop
this.clearBuffer(); // => Nested call: cleanup buffer
}
}
private onExit(state: VideoState): void {
// => Method invocation
// => Extended state (data beyond FSM state)
if (state === "Buffering") {
// => State-based guard condition
// => Comparison check
// => Comparison check
// => Conditional check
// => Branch execution based on condition
console.log("◀ Exit: Buffering complete"); // => Output for verification
// => Debug/audit output
// => Log for observability
this.trackAnalytics("buffer_complete"); // => Nested call: analytics
}
}
// Nested helper methods called by entry/exit actions
private startBuffering(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(" → Downloading video data"); // => Nested action
// => Log for observability
this.bufferLevel = 50; // => Mock buffer
}
private showBufferingIndicator(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(" → Display buffering spinner"); // => Nested action
// => Log for observability
}
private hideBufferingIndicator(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(" → Hide buffering spinner"); // => Nested action
// => Log for observability
}
private resumePlayback(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(" → Resume video playback"); // => Nested action
// => Log for observability
}
private pausePlayback(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(" → Pause video"); // => Nested action
// => Log for observability
}
private stopPlayback(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(" → Stop video"); // => Nested action
// => Log for observability
}
private clearBuffer(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(" → Clear buffer memory"); // => Nested action
// => Log for observability
this.bufferLevel = 0; // => Statement execution
// => Access instance property
// => Access instance property
}
private trackAnalytics(event: string): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(` → Track analytics: ${event}`); // => Nested action
// => FSM state management logic
// => Log for observability
}
}
// Usage
const video = new VideoPlayer(); // => state: "Stopped"
video.handleEvent("play"); // => Stopped → Buffering
// => Processes events, triggers transitions
// => Output:
// ▶ Entry: Start buffering
// → Downloading video data
// → Display buffering spinner
video.handleEvent("buffer_complete"); // => Buffering → Playing
// => Processes events, triggers transitions
// => Output:
// ◀ Exit: Buffering complete
// → Track analytics: buffer_complete
// ▶ Entry: Start playback
// → Resume video playback
// → Hide buffering spinner
// → Track analytics: play
Key Takeaway: Entry/exit actions can orchestrate multiple operations by calling helper methods. This enables rich state lifecycle behavior (UI updates, analytics, resource management) without cluttering transition logic.
Why It Matters: Nested actions organize complex state behavior. When YouTube analyzed video player bugs, they found state transition code mixed UI updates, analytics, playback control, and buffer management—making bugs hard to trace. Refactoring to nested entry/exit actions (entry: call setup helpers, exit: call cleanup helpers) reduced state-related bugs by 65% because each concern was isolated and testable.
Transition Actions (Examples 24-27)
Example 24: Transition-Specific Actions
Transition actions execute for specific state transitions, carrying transition-specific logic.
TypeScript Implementation:
// Shopping cart FSM with transition actions
type CartState = "Empty" | "HasItems" | "CheckingOut" | "OrderPlaced"; // => Four states
// => Type system ensures only valid states used
type CartEvent = "add_item" | "checkout" | "payment_success"; // => Three events
// => Events trigger state transitions
interface Item {
// => Type declaration defines structure
// => Begin object/config definition
id: string; // => Item ID
price: number; // => Item price
}
class ShoppingCart {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: CartState = "Empty"; // => Initial: Empty
// => FSM begins execution in Empty state
private items: Item[] = []; // => Cart items
// => Initialized alongside FSM state
private totalAmount = 0; // => Cart total
// => Initialized alongside FSM state
getCurrentState(): CartState {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns state
}
handleEvent(event: CartEvent, data?: any): void {
// => Event handler method
// => Ternary: condition ? true_branch : false_branch
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (event === "add_item" && this.state === "Empty") {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Event type check
// => Combined (state, event) guard
this.onTransition_AddFirstItem(data); // => Transition action (Empty → HasItems)
// => FSM state management logic
this.state = "HasItems"; // => Empty → HasItems
} else if (event === "add_item" && this.state === "HasItems") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
this.onTransition_AddAnotherItem(data); // => Transition action (HasItems → HasItems)
// => FSM state management logic
// => State stays HasItems (self-transition)
} else if (event === "checkout" && this.state === "HasItems") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
this.onTransition_StartCheckout(); // => Transition action (HasItems → CheckingOut)
// => FSM state management logic
this.state = "CheckingOut"; // => HasItems → CheckingOut
} else if (event === "payment_success" && this.state === "CheckingOut") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
this.onTransition_CompleteOrder(); // => Transition action (CheckingOut → OrderPlaced)
// => FSM state management logic
this.state = "OrderPlaced"; // => CheckingOut → OrderPlaced
}
}
// TRANSITION ACTION: Empty → HasItems (first item)
private onTransition_AddFirstItem(item: Item): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log("→ Transition: Add first item"); // => Output for verification
// => Debug/audit output
// => Log for observability
this.items.push(item); // => Add item to cart
this.totalAmount = item.price; // => Set total
console.log(` Cart created with ${item.id} ($${item.price})`); // => Output for verification
// => Chained method calls or nested operations
// => Debug/audit output
// => Log for observability
this.trackAnalytics("cart_started"); // => Analytics for first item
}
// TRANSITION ACTION: HasItems → HasItems (additional items)
private onTransition_AddAnotherItem(item: Item): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log("→ Transition: Add item to existing cart"); // => Output for verification
// => Debug/audit output
// => Log for observability
this.items.push(item); // => Add item
this.totalAmount += item.price; // => Update total
console.log(` Added ${item.id}, new total: $${this.totalAmount}`); // => Output for verification
// => Constructor creates new object instance
// => Debug/audit output
// => Log for observability
}
// TRANSITION ACTION: HasItems → CheckingOut
private onTransition_StartCheckout(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log("→ Transition: Start checkout process"); // => Output for verification
// => Debug/audit output
// => Log for observability
console.log(` Cart total: $${this.totalAmount} (${this.items.length} items)`); // => Output for verification
// => Chained method calls or nested operations
// => Debug/audit output
// => Log for observability
this.validateInventory(); // => Check item availability
this.calculateTax(); // => Add tax calculation
}
// TRANSITION ACTION: CheckingOut → OrderPlaced
private onTransition_CompleteOrder(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log("→ Transition: Complete order"); // => Output for verification
// => Debug/audit output
// => Log for observability
this.generateOrderId(); // => Create order ID
this.sendConfirmationEmail(); // => Email customer
this.updateInventory(); // => Reduce stock
this.trackAnalytics("purchase_complete"); // => Analytics
}
private validateInventory(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(" → Validate item availability"); // => Mock validation
// => Log for observability
}
private calculateTax(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(" → Calculate tax"); // => Mock tax calc
// => Log for observability
}
private generateOrderId(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(" → Order ID: ORD-12345"); // => Mock order ID
// => Log for observability
}
private sendConfirmationEmail(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(" → Send confirmation email"); // => Mock email
// => Log for observability
}
private updateInventory(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(" → Update inventory"); // => Mock inventory update
// => Log for observability
}
private trackAnalytics(event: string): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(` → Analytics: ${event}`); // => Mock analytics
// => FSM state management logic
// => Log for observability
}
}
// Usage
const cart = new ShoppingCart(); // => state: "Empty"
cart.handleEvent("add_item", { id: "laptop", price: 1200 }); // => Empty → HasItems
// => FSM state management logic
// => Processes events, triggers transitions
// => Output:
// → Transition: Add first item
// Cart created with laptop (\$1200)
// → Analytics: cart_started
cart.handleEvent("add_item", { id: "mouse", price: 25 }); // => HasItems → HasItems
// => FSM state management logic
// => Processes events, triggers transitions
// => Output:
// → Transition: Add item to existing cart
// Added mouse, new total: \$1225
cart.handleEvent("checkout"); // => HasItems → CheckingOut
// => Processes events, triggers transitions
// => Output:
// → Transition: Start checkout process
// Cart total: \$1225 (2 items)
// → Validate item availability
// → Calculate tax
cart.handleEvent("payment_success"); // => CheckingOut → OrderPlaced
// => Processes events, triggers transitions
// => Output:
// → Transition: Complete order
// → Order ID: ORD-12345
// → Send confirmation email
// → Update inventory
// → Analytics: purchase_complete
Key Takeaway: Transition actions execute logic specific to particular state transitions. First item (Empty → HasItems) triggers different action than additional items (HasItems → HasItems self-transition). Each transition has unique responsibilities.
Why It Matters: Transition-specific logic captures business rules. Checkout systems use transition actions (HasItems → CheckingOut) to validate inventory, calculate shipping, apply coupons—logic that only makes sense during that specific transition, not as entry/exit actions. Proper transition action placement reduces checkout abandonment by moving slow operations to appropriate transitions.
Example 25: Transition Actions with Side Effects
Transition actions often trigger external side effects (API calls, database updates, notifications).
TypeScript Implementation:
// Order fulfillment FSM with side effects
type OrderState = "Pending" | "Confirmed" | "Shipped" | "Delivered"; // => Four states
// => Type system ensures only valid states used
type OrderEvent = "confirm" | "ship" | "deliver"; // => Three events
// => Events trigger state transitions
interface Order {
// => Type declaration defines structure
// => Begin object/config definition
orderId: string; // => Order ID
customerId: string; // => Customer ID
trackingNumber?: string; // => Tracking number (after ship)
// => FSM state management logic
}
class OrderFulfillment {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: OrderState = "Pending"; // => Initial: Pending
// => FSM begins execution in Pending state
private order: Order; // => Order data
// => Initialized alongside FSM state
constructor(order: Order) {
// => Method invocation
// => Begin object/config definition
this.order = order; // => Store order
}
getCurrentState(): OrderState {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns state
}
handleEvent(event: OrderEvent): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (event === "confirm" && this.state === "Pending") {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Event type check
// => Combined (state, event) guard
this.onTransition_ConfirmOrder(); // => Transition action with side effects
this.state = "Confirmed"; // => Pending → Confirmed
} else if (event === "ship" && this.state === "Confirmed") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
this.onTransition_ShipOrder(); // => Transition action with side effects
this.state = "Shipped"; // => Confirmed → Shipped
} else if (event === "deliver" && this.state === "Shipped") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
this.onTransition_DeliverOrder(); // => Transition action with side effects
this.state = "Delivered"; // => Shipped → Delivered
}
}
// TRANSITION ACTION: Pending → Confirmed (multiple side effects)
private onTransition_ConfirmOrder(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log("→ Transition: Confirm order"); // => Output for verification
// => Debug/audit output
// => Log for observability
this.chargePayment(); // => Side effect: payment
this.sendEmailNotification("confirmation"); // => Side effect: email
this.createWarehouseTicket(); // => Side effect: warehouse system
this.updateInventoryReservation(); // => Side effect: inventory
console.log(` Order ${this.order.orderId} confirmed`); // => Output for verification
// => Debug/audit output
// => Log for observability
}
// TRANSITION ACTION: Confirmed → Shipped (multiple side effects)
private onTransition_ShipOrder(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log("→ Transition: Ship order"); // => Output for verification
// => Debug/audit output
// => Log for observability
this.order.trackingNumber = this.generateTrackingNumber(); // => Generate tracking
this.updateInventoryActual(); // => Side effect: decrement stock
this.sendEmailNotification("shipped"); // => Side effect: email
this.notifyDeliveryPartner(); // => Side effect: shipping API
console.log(` Order ${this.order.orderId} shipped (tracking: ${this.order.trackingNumber})`); // => Output for verification
// => Chained method calls or nested operations
// => Debug/audit output
// => Log for observability
}
// TRANSITION ACTION: Shipped → Delivered (multiple side effects)
private onTransition_DeliverOrder(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log("→ Transition: Deliver order"); // => Output for verification
// => Debug/audit output
// => Log for observability
this.sendEmailNotification("delivered"); // => Side effect: email
this.updateCustomerLoyaltyPoints(); // => Side effect: loyalty system
this.closeWarehouseTicket(); // => Side effect: warehouse system
this.archiveOrder(); // => Side effect: database
console.log(` Order ${this.order.orderId} delivered`); // => Output for verification
// => Debug/audit output
// => Log for observability
}
// Side effect methods (would call external APIs in real implementation)
private chargePayment(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(" → [API] Charge payment"); // => Payment gateway API
// => Log for observability
}
private sendEmailNotification(type: string): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(` → [API] Send ${type} email to ${this.order.customerId}`); // => Email service API
// => FSM state management logic
// => Log for observability
}
private createWarehouseTicket(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(" → [API] Create warehouse pick ticket"); // => Warehouse system API
// => Log for observability
}
private updateInventoryReservation(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(" → [DB] Reserve inventory"); // => Database update
// => Log for observability
}
private generateTrackingNumber(): string {
// => Method invocation
// => Extended state (data beyond FSM state)
return "TRACK-" + Date.now(); // => Mock tracking number
}
private updateInventoryActual(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(" → [DB] Decrement inventory"); // => Database update
// => Log for observability
}
private notifyDeliveryPartner(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(" → [API] Notify delivery partner"); // => Shipping API
// => Log for observability
}
private updateCustomerLoyaltyPoints(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(" → [DB] Add loyalty points"); // => Database update
// => Log for observability
}
private closeWarehouseTicket(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(" → [API] Close warehouse ticket"); // => Warehouse system API
// => Log for observability
}
private archiveOrder(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(" → [DB] Archive order"); // => Database update
// => Log for observability
}
}
// Usage
const order = new OrderFulfillment({
// => Instance creation via constructor
// => Create new instance
// => Create new instance
// => Initialize order
// => state: "Pending"
orderId: "ORD-123", // => Statement execution
customerId: "CUST-456", // => Statement execution
}); // => Statement execution
order.handleEvent("confirm"); // => Pending → Confirmed
// => Processes events, triggers transitions
// => Output:
// → Transition: Confirm order
// → [API] Charge payment
// → [API] Send confirmation email to CUST-456
// → [API] Create warehouse pick ticket
// → [DB] Reserve inventory
// Order ORD-123 confirmed
order.handleEvent("ship"); // => Confirmed → Shipped
// => Processes events, triggers transitions
// => Output:
// → Transition: Ship order
// → [DB] Decrement inventory
// → [API] Send shipped email to CUST-456
// → [API] Notify delivery partner
// Order ORD-123 shipped (tracking: TRACK-1234567890)
order.handleEvent("deliver"); // => Shipped → Delivered
// => Processes events, triggers transitions
// => Output:
// → Transition: Deliver order
// → [API] Send delivered email to CUST-456
// → [DB] Add loyalty points
// → [API] Close warehouse ticket
// → [DB] Archive order
// Order ORD-123 delivered
Key Takeaway: Transition actions orchestrate multiple external side effects (API calls, database updates, notifications) that must occur during specific state transitions. Each transition has checklist of operations to perform.
Why It Matters: Explicit transition actions ensure side effects happen reliably. Order systems experience incomplete side effects (payment charged but no inventory reserved, or warehouse notified but no customer email). Consolidating side effects into transition actions with comprehensive error handling reduces incomplete orders significantly. Transition actions become auditable checklist ensuring all required operations complete.
Example 26: Conditional Transition Actions
Transition actions can execute conditionally based on transition context or data.
TypeScript Implementation:
// User onboarding FSM with conditional transition actions
type OnboardingState = "NotStarted" | "ProfileCreated" | "Verified" | "Completed"; // => Four states
// => Type system ensures only valid states used
type OnboardingEvent = "create_profile" | "verify_email" | "complete_onboarding"; // => Three events
// => Events trigger state transitions
interface UserProfile {
// => Type declaration defines structure
// => Begin object/config definition
email: string; // => User email
isPremium: boolean; // => Premium status
referralCode?: string; // => Referral code (optional)
// => FSM state management logic
}
class UserOnboarding {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: OnboardingState = "NotStarted"; // => Initial: NotStarted
// => FSM begins execution in NotStarted state
private profile: UserProfile | null = null; // => User profile
// => Initialized alongside FSM state
getCurrentState(): OnboardingState {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns state
}
handleEvent(event: OnboardingEvent, data?: any): void {
// => Event handler method
// => Ternary: condition ? true_branch : false_branch
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
if (event === "create_profile" && this.state === "NotStarted") {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Event type check
// => Combined (state, event) guard
this.profile = data; // => Store profile
this.onTransition_CreateProfile(); // => Transition action with conditionals
this.state = "ProfileCreated"; // => NotStarted → ProfileCreated
} else if (event === "verify_email" && this.state === "ProfileCreated") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
this.onTransition_VerifyEmail(); // => Transition action with conditionals
this.state = "Verified"; // => ProfileCreated → Verified
} else if (event === "complete_onboarding" && this.state === "Verified") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
this.onTransition_CompleteOnboarding(); // => Transition action with conditionals
this.state = "Completed"; // => Verified → Completed
}
}
// TRANSITION ACTION: NotStarted → ProfileCreated (conditional logic)
private onTransition_CreateProfile(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log("→ Transition: Create profile"); // => Output for verification
// => Debug/audit output
// => Log for observability
if (this.profile!.referralCode) {
// => Conditional branch
// => Conditional check
// => Branch execution based on condition
// => Conditional: has referral?
console.log(" → Apply referral bonus"); // => Output for verification
// => Debug/audit output
// => Log for observability
this.applyReferralBonus(this.profile!.referralCode); // => Execute if referred
}
if (this.profile!.isPremium) {
// => Conditional branch
// => Conditional check
// => Branch execution based on condition
// => Conditional: premium user?
console.log(" → Enable premium features"); // => Output for verification
// => Debug/audit output
// => Log for observability
this.enablePremiumFeatures(); // => Execute if premium
} else {
// => Statement execution
// => Fallback branch
console.log(" → Show upgrade prompt"); // => Output for verification
// => Debug/audit output
// => Log for observability
this.showUpgradePrompt(); // => Execute if free tier
}
this.sendWelcomeEmail(); // => Always execute
}
// TRANSITION ACTION: ProfileCreated → Verified (conditional logic)
private onTransition_VerifyEmail(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log("→ Transition: Verify email"); // => Output for verification
// => Debug/audit output
// => Log for observability
if (this.profile!.isPremium) {
// => Conditional branch
// => Conditional check
// => Branch execution based on condition
// => Conditional: premium user?
console.log(" → Grant immediate access"); // => Output for verification
// => Debug/audit output
// => Log for observability
this.grantImmediateAccess(); // => Skip additional verification
} else {
// => Statement execution
// => Fallback branch
console.log(" → Require phone verification"); // => Output for verification
// => Debug/audit output
// => Log for observability
this.requestPhoneVerification(); // => Additional step for free tier
}
}
// TRANSITION ACTION: Verified → Completed (conditional logic)
private onTransition_CompleteOnboarding(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log("→ Transition: Complete onboarding"); // => Output for verification
// => Debug/audit output
// => Log for observability
if (this.profile!.referralCode) {
// => Conditional branch
// => Conditional check
// => Branch execution based on condition
// => Conditional: referred user?
console.log(" → Credit referrer account"); // => Output for verification
// => Debug/audit output
// => Log for observability
this.creditReferrer(this.profile!.referralCode); // => Reward referrer
}
if (this.profile!.isPremium) {
// => Conditional branch
// => Conditional check
// => Branch execution based on condition
// => Conditional: premium?
console.log(" → Assign dedicated support"); // => Output for verification
// => Debug/audit output
// => Log for observability
this.assignDedicatedSupport(); // => Premium perk
}
this.trackOnboardingComplete(); // => Always execute
}
// Helper methods
private applyReferralBonus(code: string): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(` Referral code ${code} applied`); // => Output for verification
// => Debug/audit output
// => Log for observability
}
private enablePremiumFeatures(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(` Premium features enabled`); // => Output for verification
// => Debug/audit output
// => Log for observability
}
private showUpgradePrompt(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(` Upgrade prompt displayed`); // => Output for verification
// => Debug/audit output
// => Log for observability
}
private sendWelcomeEmail(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(` Welcome email sent to ${this.profile!.email}`); // => Output for verification
// => Debug/audit output
// => Log for observability
}
private grantImmediateAccess(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(` Full access granted`); // => Output for verification
// => Debug/audit output
// => Log for observability
}
private requestPhoneVerification(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(` Phone verification required`); // => Output for verification
// => Debug/audit output
// => Log for observability
}
private creditReferrer(code: string): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(` Referrer ${code} credited`); // => Output for verification
// => Debug/audit output
// => Log for observability
}
private assignDedicatedSupport(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(` Dedicated support assigned`); // => Output for verification
// => Debug/audit output
// => Log for observability
}
private trackOnboardingComplete(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(` Analytics: onboarding_complete`); // => Output for verification
// => Debug/audit output
// => Log for observability
}
}
// Usage - Free tier user with referral
const user1 = new UserOnboarding(); // => state: "NotStarted"
user1.handleEvent("create_profile", {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
email: "user@example.com", // => Statement execution
isPremium: false, // => Statement execution
referralCode: "REF123", // => Statement execution
}); // => Statement execution
// => Output:
// → Transition: Create profile
// → Apply referral bonus
// Referral code REF123 applied
// → Show upgrade prompt
// Upgrade prompt displayed
// Welcome email sent to user@example.com
// Usage - Premium user without referral
const user2 = new UserOnboarding(); // => state: "NotStarted"
user2.handleEvent("create_profile", {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
email: "premium@example.com", // => Statement execution
isPremium: true, // => Statement execution
}); // => Statement execution
// => Output:
// → Transition: Create profile
// → Enable premium features
// Premium features enabled
// Welcome email sent to premium@example.com
Key Takeaway: Transition actions can contain conditional logic, executing different operations based on transition context (user data, flags, external state). Enables flexible state transitions adapting to varying circumstances.
Why It Matters: Conditional transition actions handle business logic variations without state explosion. When LinkedIn implemented profile completion FSMs, conditional actions (premium vs free users) prevented needing separate FSMs for each user tier. One FSM with conditional transition actions reduced code complexity by 60% while supporting 5 user tiers and 10+ feature flags.
Example 27: Transition Action Error Handling
Transition actions must handle errors gracefully, potentially reverting state or moving to error states.
TypeScript Implementation:
// File upload FSM with error handling in transition actions
type UploadState = "Idle" | "Uploading" | "ProcessingData" | "Success" | "Failed"; // => Five states
// => Type system ensures only valid states used
type UploadEvent = "start_upload" | "upload_complete" | "processing_complete"; // => Three events
// => Events trigger state transitions
class FileUpload {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: UploadState = "Idle"; // => Initial: Idle
// => FSM begins execution in Idle state
private fileName: string | null = null; // => Uploaded file name
// => Initialized alongside FSM state
private errorMessage: string | null = null; // => Error details
// => Initialized alongside FSM state
getCurrentState(): UploadState {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns state
}
getError(): string | null {
// => Accessor method definition
// => Begin object/config definition
return this.errorMessage; // => Returns error if Failed
}
handleEvent(event: UploadEvent, data?: any): void {
// => Event handler method
// => Ternary: condition ? true_branch : false_branch
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
try {
// => Statement execution
// => Begin error handling
// => Begin error handling
// => Begin object/config definition
if (event === "start_upload" && this.state === "Idle") {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Event type check
// => Combined (state, event) guard
this.fileName = data; // => Store filename
this.onTransition_StartUpload(); // => May throw error
this.state = "Uploading"; // => Idle → Uploading
} else if (event === "upload_complete" && this.state === "Uploading") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
this.onTransition_ProcessUpload(); // => May throw error
this.state = "ProcessingData"; // => Uploading → ProcessingData
} else if (event === "processing_complete" && this.state === "ProcessingData") {
// => Method signature: defines function interface
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Alternative condition
this.onTransition_FinalizeUpload(); // => May throw error
this.state = "Success"; // => ProcessingData → Success
}
} catch (error) {
// => Method signature: defines function interface
// => Catch errors
// => Catch errors
// => Begin object/config definition
this.handleTransitionError(error as Error); // => Error handler
}
}
// TRANSITION ACTION: Idle → Uploading (with error handling)
private onTransition_StartUpload(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log("→ Transition: Start upload"); // => Output for verification
// => Debug/audit output
// => Log for observability
if (!this.fileName || this.fileName.length === 0) {
// => Conditional branch
// => Logical OR: either condition can be true
// => Comparison check
// => Comparison check
// => Conditional check
// => Branch execution based on condition
// => Validation check
throw new Error("Invalid filename"); // => Throw error on failure
// => Fail fast on FSM violation
}
if (this.fileName.endsWith(".exe")) {
// => Conditional branch
// => Chained method calls or nested operations
// => Conditional check
// => Branch execution based on condition
// => Security check
throw new Error("Executable files not allowed"); // => Throw error
// => Fail fast on FSM violation
}
console.log(` Upload started: ${this.fileName}`); // => Output for verification
// => Debug/audit output
// => Log for observability
this.initializeUploadStream(); // => Begin upload
}
// TRANSITION ACTION: Uploading → ProcessingData (with error handling)
private onTransition_ProcessUpload(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log("→ Transition: Process uploaded file"); // => Output for verification
// => Debug/audit output
// => Log for observability
const fileSize = this.checkFileSize(); // => Check size
if (fileSize > 100 * 1024 * 1024) {
// => Conditional branch
// => Conditional check
// => Branch execution based on condition
// => Exceeds 100MB?
throw new Error("File too large (max 100MB)"); // => Throw error
// => FSM state management logic
// => Fail fast on FSM violation
}
if (!this.validateFileIntegrity()) {
// => Conditional branch
// => Chained method calls or nested operations
// => Conditional check
// => Branch execution based on condition
// => Check integrity
throw new Error("File corrupted during upload"); // => Throw error
// => Fail fast on FSM violation
}
console.log(` Processing file (${fileSize} bytes)`); // => Output for verification
// => Chained method calls or nested operations
// => Debug/audit output
// => Log for observability
this.runVirusScan(); // => Security scan
}
// TRANSITION ACTION: ProcessingData → Success (with error handling)
private onTransition_FinalizeUpload(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log("→ Transition: Finalize upload"); // => Output for verification
// => Debug/audit output
// => Log for observability
if (!this.generateThumbnail()) {
// => Conditional branch
// => Chained method calls or nested operations
// => Conditional check
// => Branch execution based on condition
// => Try thumbnail generation
console.warn(" Warning: Thumbnail generation failed (non-fatal)"); // => Log warning but continue
// => FSM state management logic
}
this.saveToDatabase(); // => May throw error
this.sendNotification(); // => May throw error
console.log(` Upload complete: ${this.fileName}`); // => Output for verification
// => Debug/audit output
// => Log for observability
}
// ERROR HANDLER (transitions to Failed state)
private handleTransitionError(error: Error): void {
// => Event handler method
// => Extended state (data beyond FSM state)
console.error(`✗ Transition error: ${error.message}`); // => Method invocation
this.errorMessage = error.message; // => Store error message
this.state = "Failed"; // => Any → Failed (error recovery)
this.cleanupResources(); // => Cleanup on error
console.log(` State: ${this.state}, Error: ${this.errorMessage}`); // => Output for verification
// => Debug/audit output
// => Log for observability
}
// Helper methods
private initializeUploadStream(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(" → Initialize upload stream"); // => Output for verification
// => Debug/audit output
// => Log for observability
}
private checkFileSize(): number {
// => Method invocation
// => Extended state (data beyond FSM state)
return 50 * 1024 * 1024; // => Mock: 50MB
}
private validateFileIntegrity(): boolean {
// => Method invocation
// => Extended state (data beyond FSM state)
return true; // => Mock: validation pass
}
private runVirusScan(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(" → Virus scan complete"); // => Output for verification
// => Debug/audit output
// => Log for observability
}
private generateThumbnail(): boolean {
// => Method invocation
// => Extended state (data beyond FSM state)
return true; // => Mock: thumbnail generated
}
private saveToDatabase(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(" → Saved to database"); // => Output for verification
// => Debug/audit output
// => Log for observability
}
private sendNotification(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(" → Notification sent"); // => Output for verification
// => Debug/audit output
// => Log for observability
}
private cleanupResources(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(" → Cleanup resources"); // => Output for verification
// => Debug/audit output
// => Log for observability
}
}
// Usage - Successful upload
const upload1 = new FileUpload(); // => state: "Idle"
upload1.handleEvent("start_upload", "document.pdf"); // => Idle → Uploading
// => Processes events, triggers transitions
upload1.handleEvent("upload_complete"); // => Uploading → ProcessingData
// => Processes events, triggers transitions
upload1.handleEvent("processing_complete"); // => ProcessingData → Success
// => Processes events, triggers transitions
console.log(`Final state: ${upload1.getCurrentState()}`); // => Output: Success
// => FSM state management logic
// => Pure read, no side effects
// Usage - Failed upload (invalid file type)
const upload2 = new FileUpload(); // => state: "Idle"
upload2.handleEvent("start_upload", "malware.exe"); // => Error: Executable not allowed
// => Processes events, triggers transitions
// => Output:
// → Transition: Start upload
// ✗ Transition error: Executable files not allowed
// → Cleanup resources
// State: Failed, Error: Executable files not allowed
console.log(`Final state: ${upload2.getCurrentState()}`); // => Output: Failed
// => FSM state management logic
// => Pure read, no side effects
console.log(`Error: ${upload2.getError()}`); // => Output: Executable files not allowed
// => FSM state management logic
Key Takeaway: Transition actions must handle errors gracefully. Try-catch blocks capture errors during transitions; error handlers move FSM to Failed state and cleanup resources. Distinguishes fatal errors (transition fails) from warnings (log but continue).
Why It Matters: Robust error handling prevents stuck FSMs. File upload systems experience failures where uploads leave FSMs in inconsistent states (partially uploaded files, orphaned database records, memory leaks). Adding error handlers to transition actions (catch errors → transition to Failed state → cleanup resources) significantly reduces stuck uploads. Failed state is terminal, preventing retry loops and exposing errors to users/monitoring systems.
Simple State Patterns (Examples 28-30)
Example 28: State Pattern Object-Oriented Design
The State Pattern encapsulates state-specific behavior in separate classes, delegating behavior to current state object.
TypeScript Implementation:
// TCP connection using State Pattern (OOP design)
// State interface defining common operations
interface ConnectionState {
// => Type declaration defines structure
// => Begin object/config definition
open(context: TcpConnection): void; // => Open operation
close(context: TcpConnection): void; // => Close operation
send(context: TcpConnection, data: string): void; // => Send operation
getStateName(): string; // => State name
}
// Concrete state: Closed
class ClosedState implements ConnectionState {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
open(context: TcpConnection): void {
// => Method invocation
// => Begin object/config definition
console.log("Opening connection..."); // => Log action
// => Log for observability
context.setState(new ConnectingState()); // => Transition to Connecting
// => FSM state management logic
}
close(context: TcpConnection): void {
// => Method invocation
// => Begin object/config definition
console.log("Already closed"); // => No-op
// => Log for observability
}
send(context: TcpConnection, data: string): void {
// => Method invocation
// => Begin object/config definition
throw new Error("Cannot send: connection closed"); // => Invalid operation
// => Fail fast on FSM violation
}
getStateName(): string {
// => Accessor method definition
// => Begin object/config definition
return "Closed"; // => State name
}
}
// Concrete state: Connecting
class ConnectingState implements ConnectionState {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
open(context: TcpConnection): void {
// => Method invocation
// => Begin object/config definition
console.log("Already connecting"); // => No-op
// => Log for observability
}
close(context: TcpConnection): void {
// => Method invocation
// => Begin object/config definition
console.log("Abort connection attempt"); // => Cancel connection
// => Log for observability
context.setState(new ClosedState()); // => Transition to Closed
// => FSM state management logic
}
send(context: TcpConnection, data: string): void {
// => Method invocation
// => Begin object/config definition
throw new Error("Cannot send: still connecting"); // => Invalid operation
// => Fail fast on FSM violation
}
getStateName(): string {
// => Accessor method definition
// => Begin object/config definition
return "Connecting"; // => State name
}
// Simulate connection establishment
completeConnection(context: TcpConnection): void {
// => Method invocation
// => Begin object/config definition
console.log("Connection established"); // => Log success
// => Log for observability
context.setState(new OpenState()); // => Transition to Open
// => FSM state management logic
}
}
// Concrete state: Open
class OpenState implements ConnectionState {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
open(context: TcpConnection): void {
// => Method invocation
// => Begin object/config definition
console.log("Already open"); // => No-op
// => Log for observability
}
close(context: TcpConnection): void {
// => Method invocation
// => Begin object/config definition
console.log("Closing connection..."); // => Log action
// => Log for observability
context.setState(new ClosedState()); // => Transition to Closed
// => FSM state management logic
}
send(context: TcpConnection, data: string): void {
// => Method invocation
// => Begin object/config definition
console.log(`Sending data: ${data}`); // => Send data
// => FSM state management logic
// => Log for observability
// => Actual send logic here
}
getStateName(): string {
// => Accessor method definition
// => Begin object/config definition
return "Open"; // => State name
}
}
// Context class delegating to state objects
class TcpConnection {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: ConnectionState; // => Current state object
constructor() {
// => Method invocation
// => Begin object/config definition
this.state = new ClosedState(); // => Initial state: Closed
// => FSM state management logic
}
setState(state: ConnectionState): void {
// => Accessor method definition
// => Begin object/config definition
console.log(`State transition: → ${state.getStateName()}`); // => Output for verification
// => Chained method calls or nested operations
// => Debug/audit output
// => Log for observability
this.state = state; // => Change state object
}
getStateName(): string {
// => Accessor method definition
// => Begin object/config definition
return this.state.getStateName(); // => Delegate to state
}
// Public API methods delegate to current state
open(): void {
// => Method invocation
// => Begin object/config definition
this.state.open(this); // => Delegate to state
}
close(): void {
// => Method invocation
// => Begin object/config definition
this.state.close(this); // => Delegate to state
}
send(data: string): void {
// => Method invocation
// => Begin object/config definition
this.state.send(this, data); // => Delegate to state
}
}
// Usage
const connection = new TcpConnection(); // => state: Closed
console.log(`Current: ${connection.getStateName()}`); // => Output: Closed
// => FSM state management logic
connection.open(); // => Closed → Connecting
// => Output:
// Opening connection...
// State transition: → Connecting
try {
// => Statement execution
// => Begin error handling
// => Begin error handling
// => Begin object/config definition
connection.send("Hello"); // => Invalid: cannot send while Connecting
} catch (error) {
// => Method signature: defines function interface
// => Catch errors
// => Catch errors
// => Begin object/config definition
console.log(`Error: ${(error as Error).message}`); // => Output: Cannot send: still connecting
// => FSM state management logic
}
// Simulate connection completing
const connectingState = new ConnectingState(); // => State variable initialization
// => Create new instance
// => Create new instance
// => Initialize connectingState
connectingState.completeConnection(connection); // => Connecting → Open
// => Output:
// Connection established
// State transition: → Open
connection.send("Hello"); // => Valid: send while Open
// => Output: Sending data: Hello
connection.close(); // => Open → Closed
// => Output:
// Closing connection...
// State transition: → Closed
Key Takeaway: State Pattern encapsulates state-specific behavior in separate classes. Context delegates operations to current state object. Adding new states requires creating new state class, not modifying existing code (Open/Closed Principle).
Why It Matters: State Pattern reduces conditional complexity. Document editing systems experience numerous if-statements checking document state (ReadOnly, Editing, Reviewing, etc.) scattered across many classes. Refactoring to State Pattern (one class per document state) reduces cyclomatic complexity significantly per method, eliminates state-related bugs, and makes adding new states (Collaborative mode) trivial.
Example 29: Hierarchical State Machines (Nested States)
Complex FSMs benefit from hierarchical states where substates inherit parent state behavior.
TypeScript Implementation:
// Alarm system with hierarchical states
// Parent state: Armed (has substates)
type ArmedSubstate = "ArmedAway" | "ArmedStay" | "ArmedNight"; // => Type declaration defines structure
// => Assign value
// => Assign value
// Parent state: Disarmed (has substates)
type DisarmedSubstate = "DisarmedNormal" | "DisarmedMaintenance"; // => Type declaration defines structure
// => Assign value
// => Assign value
// Top-level states
type AlarmState = ArmedSubstate | DisarmedSubstate | "Triggered"; // => Type declaration defines structure
// => Assign value
// => Assign value
// => Enum-like union type for state values
// => Type system ensures only valid states used
type AlarmEvent = "arm_away" | "arm_stay" | "arm_night" | "disarm" | "sensor_triggered" | "enter_maintenance"; // => Type declaration defines structure
// => Assign value
// => Assign value
// => Defines event alphabet for FSM
// => Events trigger state transitions
class HierarchicalAlarm {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: AlarmState = "DisarmedNormal"; // => Initial: DisarmedNormal
// => FSM begins execution in DisarmedNormal state
getCurrentState(): AlarmState {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns state
}
handleEvent(event: AlarmEvent): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
console.log(`Event: ${event} in state ${this.state}`); // => Output for verification
// => Debug/audit output
// => Log for observability
// Parent state behavior: ANY Armed substate can be triggered
if (this.isArmed() && event === "sensor_triggered") {
// => Event type guard condition
// => Logical AND: both conditions must be true
// => Comparison check
// => Comparison check
// => Event type check
this.state = "Triggered"; // => Any Armed → Triggered
this.soundAlarm(); // => Parent behavior
return; // => Statement execution
}
// Parent state behavior: ANY state can disarm
if (event === "disarm") {
// => Event type guard condition
// => Comparison check
// => Comparison check
// => Event type check
this.state = "DisarmedNormal"; // => Any → DisarmedNormal
this.stopAlarm(); // => Parent behavior
return; // => Statement execution
}
// Specific state transitions
if (this.isDisarmed()) {
// => Conditional branch
// => Chained method calls or nested operations
// => Conditional check
// => Branch execution based on condition
if (event === "arm_away") {
// => Event type guard condition
// => Comparison check
// => Comparison check
// => Event type check
this.state = "ArmedAway"; // => Disarmed → ArmedAway
this.activateSensors("all"); // => Specific behavior
} else if (event === "arm_stay") {
// => Method signature: defines function interface
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "ArmedStay"; // => Disarmed → ArmedStay
this.activateSensors("perimeter"); // => Specific behavior
} else if (event === "arm_night") {
// => Method signature: defines function interface
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "ArmedNight"; // => Disarmed → ArmedNight
this.activateSensors("night_mode"); // => Specific behavior
} else if (event === "enter_maintenance") {
// => Method signature: defines function interface
// => Comparison check
// => Comparison check
// => Alternative condition
this.state = "DisarmedMaintenance"; // => DisarmedNormal → DisarmedMaintenance
}
}
}
// Helper: Check if in ANY Armed substate (parent state check)
private isArmed(): boolean {
// => Method invocation
// => Extended state (data beyond FSM state)
return this.state === "ArmedAway" || this.state === "ArmedStay" || this.state === "ArmedNight"; // => State transition execution
// => Logical OR: either condition can be true
// => Access instance property
// => Access instance property
// => Return current state value
// => Hierarchical check: in parent "Armed" state?
}
// Helper: Check if in ANY Disarmed substate (parent state check)
private isDisarmed(): boolean {
// => Method invocation
// => Extended state (data beyond FSM state)
return this.state === "DisarmedNormal" || this.state === "DisarmedMaintenance"; // => State transition execution
// => Logical OR: either condition can be true
// => Access instance property
// => Access instance property
// => Return current state value
// => Hierarchical check: in parent "Disarmed" state?
}
private soundAlarm(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(" 🚨 ALARM TRIGGERED!"); // => Parent state action
// => Log for observability
}
private stopAlarm(): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(" ✓ Alarm silenced"); // => Parent state action
// => Log for observability
}
private activateSensors(mode: string): void {
// => Method invocation
// => Extended state (data beyond FSM state)
console.log(` → Activate sensors: ${mode}`); // => Substate-specific action
// => FSM state management logic
// => Log for observability
}
}
// Usage
const alarm = new HierarchicalAlarm(); // => state: DisarmedNormal
alarm.handleEvent("arm_away"); // => DisarmedNormal → ArmedAway
// => Processes events, triggers transitions
// => Output:
// Event: arm_away in state DisarmedNormal
// → Activate sensors: all
alarm.handleEvent("sensor_triggered"); // => ArmedAway → Triggered (parent transition)
// => FSM state management logic
// => Processes events, triggers transitions
// => Output:
// Event: sensor_triggered in state ArmedAway
// 🚨 ALARM TRIGGERED!
alarm.handleEvent("disarm"); // => Triggered → DisarmedNormal (parent transition)
// => FSM state management logic
// => Processes events, triggers transitions
// => Output:
// Event: disarm in state Triggered
// ✓ Alarm silenced
alarm.handleEvent("arm_stay"); // => DisarmedNormal → ArmedStay
// => Processes events, triggers transitions
// => Output:
// Event: arm_stay in state DisarmedNormal
// → Activate sensors: perimeter
alarm.handleEvent("sensor_triggered"); // => ArmedStay → Triggered (parent transition)
// => FSM state management logic
// => Processes events, triggers transitions
// => Output:
// Event: sensor_triggered in state ArmedStay
// 🚨 ALARM TRIGGERED!
Key Takeaway: Hierarchical FSMs group related substates under parent states. Parent state defines common behavior (ANY Armed substate can trigger alarm). Substates specialize parent behavior (ArmedAway activates all sensors, ArmedStay only perimeter). Reduces duplication and makes state relationships explicit.
Why It Matters: Hierarchical states prevent state explosion. When ADT (security systems) modeled alarm modes, flat FSM required 15 states and 100+ transitions. Hierarchical FSM (Armed{Away,Stay,Night}, Disarmed{Normal,Maintenance}, Triggered) reduced to 6 states and 20 transitions while supporting same functionality. Parent state transitions (disarm from ANY state) eliminated 10 duplicate transition rules.
Example 30: Simple FSM Table-Driven Implementation
Transition tables enable data-driven FSM implementation, separating state logic from implementation.
TypeScript Implementation:
// Turnstile FSM using transition table
type TurnstileState = "Locked" | "Unlocked"; // => Two states
// => Type system ensures only valid states used
type TurnstileEvent = "coin" | "push"; // => Two events
// => Events trigger state transitions
// Transition table: [currentState][event] => newState
const transitionTable: Record<TurnstileState, Partial<Record<TurnstileEvent, TurnstileState>>> = {
// => State variable initialization
// => Assign value
// => Assign value
// => Type: transition table structure
// => Maps (currentState, event) -> nextState
Locked: {
// => Statement execution
// => Begin object/config definition
coin: "Unlocked", // => Locked + coin → Unlocked
push: "Locked", // => Locked + push → Locked (self-transition)
},
Unlocked: {
// => Statement execution
// => Begin object/config definition
coin: "Unlocked", // => Unlocked + coin → Unlocked (self-transition)
push: "Locked", // => Unlocked + push → Locked
},
};
// Action table: [currentState][event] => action to execute
const actionTable: Record<TurnstileState, Partial<Record<TurnstileEvent, () => void>>> = {
// => State variable initialization
// => Additional context for complex operation
// => Assign value
// => Assign value
// => Type: transition table structure
// => Maps (currentState, event) -> nextState
Locked: {
// => Statement execution
// => Begin object/config definition
coin: () => console.log(" → Unlock turnstile"), // => Action for Locked + coin
// => FSM state management logic
// => Log for observability
push: () => console.log(" → Alarm: Push while locked"), // => Action for Locked + push
// => FSM state management logic
// => Log for observability
},
Unlocked: {
// => Statement execution
// => Begin object/config definition
coin: () => console.log(" → Return coin (already unlocked)"), // => Action for Unlocked + coin
// => FSM state management logic
// => Log for observability
push: () => console.log(" → Lock turnstile after passage"), // => Action for Unlocked + push
// => FSM state management logic
// => Log for observability
},
};
class TurnstileFSM {
// => Class encapsulates FSM logic
// => State machine implementation class
// => Encapsulates state + transition logic
private state: TurnstileState = "Locked"; // => Initial: Locked
// => FSM begins execution in Locked state
getCurrentState(): TurnstileState {
// => Accessor method definition
// => Query method: read current FSM state
// => Pure read, no side effects
return this.state; // => Returns state
}
handleEvent(event: TurnstileEvent): void {
// => Event handler method
// => Event handler: main FSM dispatch method
// => Processes events, triggers transitions
console.log(`Event: ${event} (State: ${this.state})`); // => Output for verification
// => Chained method calls or nested operations
// => Debug/audit output
// => Log for observability
// Look up next state in transition table
const nextState = transitionTable[this.state][event]; // => Table lookup
if (!nextState) {
// => Conditional branch
// => Conditional check
// => Branch execution based on condition
console.log(" ✗ Invalid transition"); // => No transition defined
// => Log for observability
return; // => Statement execution
}
// Execute action from action table
const action = actionTable[this.state][event]; // => Table lookup
if (action) {
// => Conditional branch
// => Conditional check
// => Branch execution based on condition
action(); // => Execute action
}
// Transition to next state
if (this.state !== nextState) {
// => State-based guard condition
// => Comparison check
// => Comparison check
// => Conditional check
// => Branch execution based on condition
console.log(` State: ${this.state} → ${nextState}`); // => Log transition
// => FSM state management logic
// => Log for observability
this.state = nextState; // => Change state
}
}
}
// Usage
const turnstile = new TurnstileFSM(); // => state: "Locked"
console.log(`Initial: ${turnstile.getCurrentState()}`); // => Output: Locked
// => FSM state management logic
// => Pure read, no side effects
turnstile.handleEvent("push"); // => Locked + push → Locked
// => Processes events, triggers transitions
// => Output:
// Event: push (State: Locked)
// → Alarm: Push while locked
turnstile.handleEvent("coin"); // => Locked + coin → Unlocked
// => Processes events, triggers transitions
// => Output:
// Event: coin (State: Locked)
// → Unlock turnstile
// State: Locked → Unlocked
turnstile.handleEvent("coin"); // => Unlocked + coin → Unlocked
// => Processes events, triggers transitions
// => Output:
// Event: coin (State: Unlocked)
// → Return coin (already unlocked)
turnstile.handleEvent("push"); // => Unlocked + push → Locked
// => Processes events, triggers transitions
// => Output:
// Event: push (State: Unlocked)
// → Lock turnstile after passage
// State: Unlocked → Locked
console.log(`Final: ${turnstile.getCurrentState()}`); // => Output: Locked
// => FSM state management logic
// => Pure read, no side effects
Key Takeaway: Table-driven FSMs separate state logic (transition tables, action tables) from implementation (handleEvent method). Adding states/transitions requires only table updates, not code changes. Transition table defines valid transitions; action table defines operations to execute.
Why It Matters: Table-driven FSMs enable non-programmers to configure state logic. Call routing systems use transition tables to allow customer support to configure call flows (IVR menus, voicemail, transfers) without engineering involvement. Business users edit JSON configuration files defining transition tables; FSM engine executes flows. This reduces call flow deployment time significantly.
This completes the beginner-level FSM by-example tutorial with 30 comprehensive examples covering introduction to FSMs, basic states and transitions, events and triggers, guards and conditions, entry/exit actions, transition actions, and simple state patterns (0-40% coverage).