Theory

Finite State Machine

A finite set of states + transitions — models everything from regex to TCP

A finite state machine (FSM) is a system in one of a fixed number of states at any time, transitioning to other states in response to inputs. FSMs model lexers, regular expressions, network protocols (TCP), UI workflows, parsers, game character behavior, and circuit logic. The simplest non-trivial computational model — and one of the most widely deployed.

  • VariantsDFA, NFA, Mealy, Moore, hierarchical (HSM)
  • Computational powerRecognizes regular languages — strictly less than Turing machines
  • MemoryConstant — only "current state" persists
  • ConstructionFrom regex (Thompson), tabular (Moore), code (state pattern)
  • Used inLexers, regex engines, TCP, UI, game AI, hardware controllers
  • Famous limitationCannot match balanced parentheses (need a stack)

Interactive visualization

Press play, or step through manually. The visualization is yours to drive — try it before reading on.

Open visualization fullscreen ↗

Watch the 60-second explainer

A condensed visual walkthrough — narrated, captioned, under a minute.

What is a finite state machine?

A formal FSM is defined by:

  • A finite set of states Q.
  • A finite alphabet Σ of input symbols.
  • A transition function δ: Q × Σ → Q (for DFA) or Q × Σ → 2^Q (for NFA).
  • An initial state q₀ ∈ Q.
  • A set of accept states F ⊆ Q.

The machine starts in q₀, reads input symbols one at a time, and follows transitions. After consuming all input, if the current state is in F, the input is accepted. That's the entire model.

Example — match strings ending in "001"

           0          0          1
   ┌──── q0 ──────► q1 ──────► q2 ──────► q3 (accept)
   │  ▲              │           │           │
   1  │              1           1           0
   │  └──────────────┴───────────┘           │
   ▼                                          │
  q0 ◄────────────────────────────────────────┘
                       0 from any non-q3 state restarts

State meaning:
  q0 — haven't seen any prefix of '001'
  q1 — seen '0'
  q2 — seen '00'
  q3 — seen '001' (accept)

Four states, four input characters, transitions defined for each (state, input) pair. Linear in input length to simulate.

DFA vs NFA

DFANFA
Active states at any momentExactly oneOne or more
Transitions per (state, input)Exactly oneZero, one, or many
Epsilon transitionsNoYes
Construction from regexHard (subset construction)Easy (Thompson)
State count for same regexUp to 2^n moreLinear in regex
Match speedFaster (one state to track)Slower (set of states to track)
Used inLexers (compile-time DFA)Backtracking regex (runtime NFA)

Both recognize exactly the same languages — regular languages, by definition. The conversion between them is mechanical: subset construction (NFA→DFA) and the trivial inclusion (every DFA is an NFA).

Mealy vs Moore — output models

MooreMealy
Output depends onCurrent state onlyCurrent state + input
State count for same behaviorLargerSmaller
Easier to reason aboutYes — output is "where am I"No — output couples to input
Hardware preferenceBoth common; Moore is glitch-freeMealy more common in software
ExamplesTraffic light (red/yellow/green is the state)Parser emitting tokens (state + input → token)

When to use an FSM in code

  • Network protocols. TCP has formal FSM states (CLOSED, LISTEN, SYN_SENT, ESTABLISHED, FIN_WAIT_1, ...). UDP doesn't have any state. Most protocols document their state machine explicitly.
  • UI workflows. "User is editing → submitting → submitted" or "logged out → logging in → 2FA pending → logged in." XState is a popular JS library for FSM-driven UI.
  • Lexers and parsers. Lexers are typically compiled to a DFA from regex rules. Parsers use stacks (pushdown automata) but the lexer underneath is pure FSM.
  • Game AI. NPC behavior — idle, patrol, alert, attack, retreat — modeled as states with transitions on triggers. Hierarchical FSMs (HSMs) handle nested behaviors.
  • Embedded systems. Microcontroller firmware almost always has an FSM at its core — power management, sensor reading cycle, motor control sequences.
  • Validation rules. Email format, URL format, credit card number format — all regex, all FSM under the hood.

JavaScript: implementing an FSM

// Manual implementation
class TCPStateMachine {
  constructor() { this.state = 'CLOSED'; }

  transition(event) {
    const transitions = {
      CLOSED: {
        ACTIVE_OPEN:  'SYN_SENT',
        PASSIVE_OPEN: 'LISTEN',
      },
      LISTEN: {
        SYN_RECEIVED: 'SYN_RCVD',
        CLOSE:        'CLOSED',
      },
      SYN_SENT: {
        SYN_ACK:      'ESTABLISHED',  // simplified — real TCP also requires sending ACK
        CLOSE:        'CLOSED',
      },
      SYN_RCVD: {
        ACK:          'ESTABLISHED',
        CLOSE:        'FIN_WAIT_1',
      },
      ESTABLISHED: {
        CLOSE:        'FIN_WAIT_1',
        REMOTE_FIN:   'CLOSE_WAIT',
      },
      // ... etc
    };
    const next = transitions[this.state]?.[event];
    if (!next) throw new Error(`Invalid transition: ${event} from ${this.state}`);
    this.state = next;
    return this.state;
  }

  isConnected() { return this.state === 'ESTABLISHED'; }
}

// Using a library (XState style)
import { createMachine, interpret } from 'xstate';

const trafficLightMachine = createMachine({
  id: 'trafficLight',
  initial: 'red',
  states: {
    red:    { on: { TIMER: 'green' } },
    green:  { on: { TIMER: 'yellow' } },
    yellow: { on: { TIMER: 'red' } },
  },
});

const service = interpret(trafficLightMachine).start();
service.send('TIMER');  // red → green
service.send('TIMER');  // green → yellow
service.send('TIMER');  // yellow → red

Python: simple FSM with a dict

class FSM:
    def __init__(self, transitions, initial, accepts):
        self.transitions = transitions  # {(state, input): next_state}
        self.state = initial
        self.accepts = accepts

    def step(self, input_symbol):
        key = (self.state, input_symbol)
        if key not in self.transitions:
            raise ValueError(f"No transition from {self.state} on {input_symbol}")
        self.state = self.transitions[key]
        return self.state

    def accept(self, input_string):
        for c in input_string:
            self.step(c)
        return self.state in self.accepts

# Recognizer for strings ending in '001'
fsm = FSM(
    transitions={
        ('q0', '0'): 'q1', ('q0', '1'): 'q0',
        ('q1', '0'): 'q2', ('q1', '1'): 'q0',
        ('q2', '0'): 'q2', ('q2', '1'): 'q3',
        ('q3', '0'): 'q1', ('q3', '1'): 'q0',
    },
    initial='q0',
    accepts={'q3'},
)
print(fsm.accept('110001'))  # True

What FSMs cannot do

FSMs have constant memory — only the current state. Two famous things they can't do:

  1. Match balanced parentheses. (((()))) requires counting opens minus closes. An FSM with k states can only count up to k. Push-down automata (FSMs with a stack) handle this — they recognize context-free languages.
  2. Recognize palindromes of arbitrary length. Requires comparing positions far apart. An FSM has no memory of what it saw earlier.

The Chomsky hierarchy classifies these — regular (FSM) ⊂ context-free (PDA) ⊂ context-sensitive ⊂ recursively enumerable (Turing machine). Each level adds expressive power.

Hierarchical state machines

A regular FSM with 100 states is hard to read. Hierarchical FSMs (HSMs, statecharts — Harel 1987) nest states inside states. Common pattern — a "playing" superstate contains "running," "jumping," "swimming" substates. Events and transitions defined at the superstate apply to all substates (e.g., "PAUSE" works in any of the three).

HSMs reduce state count exponentially for complex behaviors. UML statecharts and IBM's Rhapsody are formal HSM tools; XState (JS) and SCXML (W3C) are widely-used implementations.

Common FSM bugs

  • Implicit state via booleans. Three booleans (isLoggedIn, isLoading, hasError) represent 8 combinations, but only 4 are valid. The implicit FSM is buggy because invalid combinations are reachable. Make the states explicit.
  • Missing transitions. Real systems have many event sources; forgetting to handle some events in some states leads to "stuck" or undefined behavior. List every state × event combination during design.
  • Side effects in transitions vs entry actions. Mealy machines emit on transition; Moore machines emit on state entry. Mixing them produces hard-to-reason-about side effects (an action runs twice or zero times depending on the path). Pick one model and stick with it.
  • Race conditions in concurrent FSMs. Two events arriving simultaneously at the same FSM cause non-deterministic state. Use single-threaded event loops, atomic state updates, or actor-model isolation.
  • State explosion. A naive FSM for a complex system has too many states to fit on a whiteboard. Refactor into HSMs or compose multiple smaller FSMs (hierarchically or in parallel).
  • Tight coupling between states and UI. The FSM should describe behavior; views should observe state changes. Mixing them produces fragile code. XState explicitly separates state machines from UI rendering.

Frequently asked questions

What's the difference between a DFA and an NFA?

DFA (Deterministic Finite Automaton) — at any moment, exactly one state is active, and there's exactly one transition per input symbol per state. NFA (Non-deterministic) — multiple states can be active simultaneously, and a state can have multiple transitions for the same input (or epsilon transitions for "no input"). Both recognize the same class of languages (regular). NFAs are easier to construct from regex; DFAs are faster to simulate. Subset construction converts NFA to DFA but can produce exponentially many states.

What's the difference between Mealy and Moore machines?

Both are FSM variants distinguished by output. Moore — output depends only on the current state. Mealy — output depends on the current state AND the input. Mealy is more compact (fewer states for the same behavior); Moore is simpler to reason about. Hardware designers use both; software FSMs typically don't formalize the distinction.

When should I use an FSM in code?

When your code has discrete modes with rules about which transitions are valid. Examples — TCP connection states (CLOSED → SYN_SENT → ESTABLISHED → ...), UI workflows (form not submitted → submitting → submitted), game character (idle → walking → attacking → dead), parser modes (in string vs in escape vs done). Without an explicit FSM, these often degenerate into spaghetti booleans.

What can FSMs not do?

Recognize languages requiring memory of unbounded depth. Balanced parentheses (<code>{{{}}}</code>) require counting open and close — beyond constant memory. Recognizing palindromes of arbitrary length — same. These need a pushdown automaton (stack) or full Turing machine. The pumping lemma formalizes which languages are regular vs not.

How is a regex related to an FSM?

Every regular expression compiles to an FSM (an NFA, typically converted to a DFA). Every FSM corresponds to a regular expression. They're mathematically equivalent. Regex matching IS FSM simulation — when you call <code>'abc'.match(/.../)</code>, you're running an FSM constructed from the pattern.

What's a hierarchical state machine?

A nested FSM where states can themselves contain sub-state machines. Used in UML statecharts (Harel), embedded systems, and game AI. A "playing" state might have substates "running," "jumping," "swimming." Reduces state explosion in complex systems — instead of ten cross-cutting states, you have two levels of three-state machines.

How do I detect dead states?

A dead state is one from which no accepting state is reachable. Run a graph search backward from accept states; any state not reached is dead. Common in compiler-generated DFAs after subset construction; some optimizers eliminate dead states to shrink the table.