All topics
Patternsadvanced

State Machine Pattern for UI Logic

Modeling a component's behavior as a finite set of distinct states and explicit transitions between them, instead of scattered boolean flags.

The state machine pattern models a system — often a UI component, but equally applicable to any process with distinct phases — as a finite set of named states (like idle, loading, success, error) along with explicit, well-defined transitions between them, rather than representing the same information as a scattered collection of independent boolean flags (isLoading, hasError, isSuccess) that can, if not carefully managed, end up in combinations that don't actually make logical sense (like isLoading and hasError both being true simultaneously). This pattern has become increasingly popular in frontend development specifically because UI components very naturally have a small number of mutually exclusive modes.

A state machine is like a well-designed vending machine's internal logic: it can only be in one specific mode at a time — waiting for coins, dispensing, out of stock — and it only accepts the specific inputs that are valid for its current mode, refusing to even consider a 'dispense' button press while it's still in 'waiting for coins' mode, rather than a poorly-designed machine that tracks a pile of independent switches that could theoretically all be flipped on at once, leading to nonsensical states nobody actually designed for.

Key Concepts

1
The core problem with the boolean-flags approach is that as a component's logic grows, the *possible* combinations of flags grows exponentially (N independent booleans yield 2^N possible states), even though only a small handful of those combinations are ever actually valid or reachable in practice — leaving a lot of implicit, undocumented assumptions about which combinations 'shouldn't' happen, which inevitably get violated eventually by a bug, an unusual sequence of events, or a rushed feature addition. A state machine instead makes the actual valid states explicit and enumerable (there are exactly N *named* states, not 2^N flag combinations), and, critically, defines exactly which transitions between states are even allowed, making genuinely invalid states literally unrepresentable rather than merely 'supposed to not happen.'
2
A simple hand-rolled state machine can be implemented as a plain object mapping each state to the specific events it accepts and which state each event transitions to, with a small driver function that looks up the current state's allowed transitions and rejects (or ignores) any event that isn't a valid transition from the current state — this alone, even without a dedicated library, forces you to think explicitly about every state and its exact valid transitions up front, which tends to surface edge cases much earlier than the boolean-flags approach would.
3
Libraries like XState formalize this further with the broader concept of 'statecharts' (an extension of finite state machines that also supports hierarchical/nested states, parallel states, and richer context data alongside the state itself), but the core underlying idea — model your component's behavior as an explicit set of states and transitions rather than implicit flag combinations — is valuable and applicable even without adopting a dedicated library, and it's exactly the mental model interviewers are checking for when they ask you to reason about a loading/error/success UI flow.