low-level-design4 min read

State Design Pattern Tutorial from Scratch (2026)

State Design Pattern Tutorial from Scratch (2026)

Published:  |  Category: Low Level Design  |  Reading time: ~15 min
State Design Pattern Tutorial from Scratch (2026)

The State pattern allows an object to alter its behavior when its internal state changes. The object will appear to change its class. It is essentially a finite state machine implemented through object composition.

This pattern is useful when an object has multiple states with distinct behaviors and transitions between them. It eliminates large conditional statements that check the current state before each operation.

State Machines, Context, and Finite State Design

The State pattern uses a Context that holds a reference to a current State object. Each concrete state implements the behavior for the Context in that particular state and defines valid transitions to other states. The Context delegates all state-specific behavior to the current state object.

This approach turns each state into a first-class object with its own behavior logic. Adding a new state means creating a new class without modifying existing states or the context, as long as the interface remains consistent.

// State interface
class State {
  constructor(name) {
    this.name = name;
  }
  insertQuarter() {}
  ejectQuarter() {}
  turnCrank() {}
  dispense() {}
}

// Context
class VendingMachine {
  constructor() {
    this.soldOutState = new SoldOutState(this);
    this.noQuarterState = new NoQuarterState(this);
    this.hasQuarterState = new HasQuarterState(this);
    this.soldState = new SoldState(this);
    this.state = this.soldOutState;
    this.count = 0;
  }
  setState(state) {
    this.state = state;
  }
  refill(count) {
    this.count = count;
    this.state = this.count > 0 ? this.noQuarterState : this.soldOutState;
  }
  insertQuarter() { this.state.insertQuarter(); }
  ejectQuarter() { this.state.ejectQuarter(); }
  turnCrank() { this.state.turnCrank(); this.state.dispense(); }
}

// Concrete states
class NoQuarterState extends State {
  constructor(machine) {
    super('NoQuarter');
    this.machine = machine;
  }
  insertQuarter() {
    console.log('Quarter inserted');
    this.machine.setState(this.machine.hasQuarterState);
  }
}

class HasQuarterState extends State {
  constructor(machine) {
    super('HasQuarter');
    this.machine = machine;
  }
  ejectQuarter() {
    console.log('Quarter returned');
    this.machine.setState(this.machine.noQuarterState);
  }
  turnCrank() {
    console.log('Crank turned');
    this.machine.setState(this.machine.soldState);
  }
}

class SoldState extends State {
  constructor(machine) {
    super('Sold');
    this.machine = machine;
  }
  dispense() {
    if (this.machine.count > 0) {
      this.machine.count--;
      console.log('Item dispensed');
      this.machine.setState(
        this.machine.count > 0 ? this.machine.noQuarterState : this.machine.soldOutState
      );
    }
  }
}

class SoldOutState extends State {
  constructor(machine) {
    super('SoldOut');
    this.machine = machine;
  }
  insertQuarter() {
    console.log('Machine is sold out');
  }
}

const machine = new VendingMachine();
machine.refill(2);
machine.insertQuarter();
machine.turnCrank();
machine.insertQuarter();
machine.turnCrank();
machine.insertQuarter();

Vending Machine Implementation and State Transitions

The vending machine example demonstrates four states: NoQuarter, HasQuarter, Sold, and SoldOut. Each state defines valid actions and transitions. The Context (VendingMachine) delegates operations to the current state object.

This design makes state transitions explicit and encapsulated within each state class. The vending machine code contains no if-else chains checking the current state, making it easy to add new states like a 'Winner' state that dispenses an extra item.

// Adding a new state: WinnerState
class WinnerState extends State {
  constructor(machine) {
    super('Winner');
    this.machine = machine;
  }
  dispense() {
    console.log('YOU WIN! Two items dispensed!');
    this.machine.count -= 2;
    console.log('Items dispensed');
    this.machine.setState(
      this.machine.count > 0 ? this.machine.noQuarterState : this.machine.soldOutState
    );
  }
}

// Modify HasQuarterState to transition to WinnerState randomly
class HasQuarterStateWithWinner extends HasQuarterState {
  constructor(machine) {
    super(machine);
    this.machine = machine;
  }
  turnCrank() {
    console.log('Crank turned');
    const winner = Math.random() < 0.3;
    this.machine.setState(
      winner ? this.machine.winnerState : this.machine.soldState
    );
  }
}

// Add to VendingMachine constructor:
// this.winnerState = new WinnerState(this);
// this.hasQuarterState = new HasQuarterStateWithWinner(this);

// Logging the state machine
function logTransitions(machine) {
  const transitions = [];
  const origSetState = machine.setState.bind(machine);
  machine.setState = function(state) {
    transitions.push(`${machine.state.name} -> ${state.name}`);
    origSetState(state);
  };
  return transitions;
}

Frequently Asked Questions

When is the State pattern better than a switch/if-else approach?

State is better when you have multiple states with complex behaviors, frequent state additions, or state-specific logic scattered across methods. If you have 3+ states with 3+ operations each, State keeps code organized. For simple 2-state flags, conditionals suffice.

How does State differ from Strategy?

Both patterns use composition to change behavior, but their intent differs. Strategy lets the client select an algorithm from interchangeable options. State allows an object's behavior to change automatically as its internal state changes, with state transitions managed by the states themselves.

Can state objects be shared between multiple contexts?

Yes, if the state objects contain no instance-specific data (flyweight pattern). In the vending machine example, states store a reference to their machine, so they are not shareable. Stateless state objects can be shared safely across contexts.

How do you handle state transition logic?

Transitions can be defined either in the context (centralized) or in each state (decentralized). Decentralized transitions (each state decides the next state) are more common in State pattern, as they encapsulate behavior. Centralized transitions are easier to audit but require modifying the context to add states.

Originally published on Ayodhyyya. Last updated June 1, 2026.