OOP Interview Prep
Design Patterns

State Pattern

When an Object's Behavior Changes Based on What It Remembers

LinkedIn Hook

Here is a question that separates mid-level candidates from senior ones:

"What is the difference between the State pattern and the Strategy pattern?"

Most developers have used state machines without naming them. They have written giant if/else blocks that check a status field and behave differently in each branch. They have seen those blocks grow to 200 lines and become unmaintainable.

The State pattern solves exactly that problem. It extracts each "mode" into its own class. The object delegates behavior to the current state object instead of branching on a field. When the state changes, you swap the object. The conditionals disappear.

Strategy and State look nearly identical in structure. The difference is in intent and lifecycle. Interviewers know this. They use it to test whether you understand patterns at a conceptual level or just at a syntax level.

Lesson 8.6 covers the full pattern, a working vending machine, a traffic light, and a direct State vs Strategy comparison table you can use in any interview.

Read the full lesson -> [link]

#OOP #JavaScript #DesignPatterns #InterviewPrep #StateMachine


State Pattern thumbnail


What You'll Learn

  • What the State pattern is and the core problem it solves (the if/else explosion)
  • How a state machine works conceptually and how to map it to objects and classes
  • A fully working vending machine implementation in JavaScript using the State pattern
  • A traffic light example showing automatic state transitions
  • A direct State vs Strategy comparison table covering intent, lifecycle, and transition ownership
  • When to reach for the State pattern and when a simple status field is enough
  • The most common interview trap: explaining why State and Strategy are structurally similar but conceptually opposite

The Analogy That Makes It Click

Think about a traffic light.

A traffic light is always in exactly one state at a time: Red, Yellow, or Green. In each state, it behaves differently. Red stops traffic. Green allows it. Yellow warns of the transition coming. You cannot be Red and Green at the same time.

The light does not consult a large decision tree every time a car approaches. It does not ask "what is my current status, and given that status, what should I do?" It simply behaves as what it currently is. When a timer fires, it transitions to the next state, and then it behaves as that new thing.

That is the State pattern. The object delegates all behavior to a current state object. When something triggers a change, the current state object is swapped out for the next one. The object itself stays the same. Only the delegate changes.

[INTERNAL-LINK: behavioral patterns overview → Lesson 8.1: Introduction to Design Patterns]


What Is the State Pattern?

The State pattern is a behavioral design pattern that lets an object alter its behavior when its internal state changes. Instead of storing a status string and branching on it throughout the codebase, each state is encapsulated in its own class. The main object holds a reference to the current state object and delegates method calls to it.

The Gang of Four formally defined the State pattern in "Design Patterns: Elements of Reusable Object-Oriented Software" (1994). Their motivation was the same problem every developer hits when writing complex workflow objects: a single class accumulates dozens of conditionals all checking the same status variable. Adding a new state means editing every conditional in the class. That is fragile, hard to test, and easy to break.

The State pattern makes each state a closed, independent unit. Adding a new state means adding a new class, not editing existing ones. This satisfies the Open/Closed Principle from SOLID.

[INTERNAL-LINK: Open/Closed Principle in SOLID → Lesson 7.x: SOLID Principles]


What Is a State Machine?

A state machine is a model of behavior with a fixed set of states, a set of possible transitions between those states, and rules about what triggers each transition. At any moment, the machine is in exactly one state. Inputs or events cause it to transition to another state or stay in the same one.

State machines appear everywhere in software: order workflows (Pending, Confirmed, Shipped, Delivered, Cancelled), user sessions (LoggedOut, LoggingIn, LoggedIn, Locked), network connections (Closed, Listening, Established, Closing), and UI components (Idle, Loading, Success, Error).

The State pattern is one clean way to implement a state machine in OOP. The machine is the context object. Each state is a class. Transitions happen when a state method tells the context to switch to a different state object.

[CHART: State diagram - vending machine states (Idle, HasCoin, Dispensing, OutOfStock) connected by labeled transition arrows - draw manually or describe to Napkin AI]


Code Example 1 — Vending Machine (State Pattern, Full Implementation)

This is the classic State pattern example. A vending machine has four states: Idle (waiting for a coin), HasCoin (coin inserted, waiting for selection), Dispensing (dispensing the item), and OutOfStock (no items left). The same method, insertCoin() or selectProduct(), behaves completely differently depending on the current state.

// Each state is a class that implements the same interface
// insertCoin, selectProduct, dispense, refund

class IdleState {
  constructor(machine) {
    this.machine = machine;
  }

  insertCoin() {
    console.log("Coin inserted. Select a product.");
    // Transition to the next state
    this.machine.setState(this.machine.hasCoinState);
  }

  selectProduct() {
    console.log("Please insert a coin first.");
  }

  dispense() {
    console.log("Please insert a coin first.");
  }

  refund() {
    console.log("No coin to refund.");
  }
}

class HasCoinState {
  constructor(machine) {
    this.machine = machine;
  }

  insertCoin() {
    console.log("A coin is already inserted. Refunding the extra coin.");
  }

  selectProduct() {
    if (this.machine.getStock() === 0) {
      console.log("Out of stock. Refunding your coin.");
      this.machine.setState(this.machine.outOfStockState);
      return;
    }
    console.log("Product selected. Dispensing...");
    // Transition to dispensing state
    this.machine.setState(this.machine.dispensingState);
  }

  dispense() {
    console.log("Select a product first.");
  }

  refund() {
    console.log("Coin refunded.");
    // Transition back to idle
    this.machine.setState(this.machine.idleState);
  }
}

class DispensingState {
  constructor(machine) {
    this.machine = machine;
  }

  insertCoin() {
    console.log("Please wait. Dispensing in progress.");
  }

  selectProduct() {
    console.log("Please wait. Dispensing in progress.");
  }

  dispense() {
    this.machine.reduceStock();

    if (this.machine.getStock() === 0) {
      console.log("Item dispensed. Machine is now out of stock.");
      // Transition to out-of-stock state
      this.machine.setState(this.machine.outOfStockState);
    } else {
      console.log("Item dispensed. Ready for next customer.");
      // Transition back to idle
      this.machine.setState(this.machine.idleState);
    }
  }

  refund() {
    console.log("Cannot refund while dispensing.");
  }
}

class OutOfStockState {
  constructor(machine) {
    this.machine = machine;
  }

  insertCoin() {
    console.log("Out of stock. Coin returned.");
  }

  selectProduct() {
    console.log("Out of stock.");
  }

  dispense() {
    console.log("Out of stock. Nothing to dispense.");
  }

  refund() {
    console.log("No coin inserted.");
  }
}

// Context class — holds reference to current state, delegates all calls
class VendingMachine {
  constructor(initialStock) {
    // Create all possible state objects upfront
    this.idleState = new IdleState(this);
    this.hasCoinState = new HasCoinState(this);
    this.dispensingState = new DispensingState(this);
    this.outOfStockState = new OutOfStockState(this);

    this.stock = initialStock;

    // Set initial state based on stock level
    this.currentState = initialStock > 0 ? this.idleState : this.outOfStockState;
  }

  // Delegate all public actions to the current state object
  insertCoin() {
    this.currentState.insertCoin();
  }

  selectProduct() {
    this.currentState.selectProduct();
  }

  dispense() {
    this.currentState.dispense();
  }

  refund() {
    this.currentState.refund();
  }

  // State management methods — called by state objects to trigger transitions
  setState(newState) {
    this.currentState = newState;
  }

  getStock() {
    return this.stock;
  }

  reduceStock() {
    this.stock = Math.max(0, this.stock - 1);
  }
}

// --- Running the vending machine ---

const machine = new VendingMachine(2);

console.log("--- Customer 1 ---");
machine.insertCoin();       // "Coin inserted. Select a product."
machine.selectProduct();    // "Product selected. Dispensing..."
machine.dispense();         // "Item dispensed. Ready for next customer."

console.log("\n--- Customer 2 (last item) ---");
machine.insertCoin();       // "Coin inserted. Select a product."
machine.selectProduct();    // "Product selected. Dispensing..."
machine.dispense();         // "Item dispensed. Machine is now out of stock."

console.log("\n--- Customer 3 (machine empty) ---");
machine.insertCoin();       // "Out of stock. Coin returned."
machine.selectProduct();    // "Out of stock."

console.log("\n--- Invalid sequence ---");
const machine2 = new VendingMachine(5);
machine2.selectProduct();   // "Please insert a coin first."
machine2.dispense();        // "Please insert a coin first."
machine2.insertCoin();
machine2.insertCoin();      // "A coin is already inserted. Refunding the extra coin."
machine2.refund();          // "Coin refunded."

[IMAGE: Vending machine state diagram showing four circles (Idle, HasCoin, Dispensing, OutOfStock) with labeled arrows between them - search terms: "state machine diagram vending machine"]

[PERSONAL EXPERIENCE]: The most telling sign that State pattern is needed is when you find yourself adding the same if (this.status === "X") guard to five different methods. Each new status you add requires touching every method again. The State pattern eliminates that by making the guard implicit: the wrong state simply does not have the behavior.


Code Example 2 — Traffic Light (Automatic Transitions)

The traffic light example shows a key variation: states that transition themselves on a timer, without any external trigger calling setState. Each state schedules its own successor.

class RedState {
  constructor(light) {
    this.light = light;
    this.duration = 5000; // Red for 5 seconds
  }

  enter() {
    console.log("RED - Stop");
    // State schedules its own transition after the duration
    setTimeout(() => {
      this.light.setState(this.light.greenState);
    }, this.duration);
  }
}

class GreenState {
  constructor(light) {
    this.light = light;
    this.duration = 4000; // Green for 4 seconds
  }

  enter() {
    console.log("GREEN - Go");
    setTimeout(() => {
      this.light.setState(this.light.yellowState);
    }, this.duration);
  }
}

class YellowState {
  constructor(light) {
    this.light = light;
    this.duration = 1500; // Yellow for 1.5 seconds
  }

  enter() {
    console.log("YELLOW - Slow down");
    setTimeout(() => {
      this.light.setState(this.light.redState);
    }, this.duration);
  }
}

class TrafficLight {
  constructor() {
    this.redState = new RedState(this);
    this.greenState = new GreenState(this);
    this.yellowState = new YellowState(this);

    // Start at red
    this.currentState = this.redState;
  }

  start() {
    // Call enter() on the initial state to kick off the cycle
    this.currentState.enter();
  }

  setState(newState) {
    this.currentState = newState;
    // Immediately trigger the new state's entry behavior
    this.currentState.enter();
  }
}

// --- Running the traffic light ---
// Output (timed):
// RED - Stop     (at 0s)
// GREEN - Go     (at 5s)
// YELLOW - Slow down  (at 9s)
// RED - Stop     (at 10.5s)
// ... (cycles indefinitely)

const light = new TrafficLight();
light.start();

This example highlights how the State pattern handles self-driving transitions cleanly. The context, TrafficLight, has no timer logic at all. Each state class owns its own duration and knows which state comes next. To change the cycle, you only change the relevant state class.


Code Example 3 — Without the Pattern (The Problem It Solves)

Seeing the problem first makes the solution more meaningful. Here is a stripped-down vending machine written without the State pattern, using the status-field approach most developers reach for first.

class VendingMachineNaive {
  constructor(stock) {
    this.stock = stock;
    // All state lives in one field — drives all branching below
    this.status = stock > 0 ? "idle" : "out_of_stock";
  }

  insertCoin() {
    // Every method checks the same field
    if (this.status === "idle") {
      console.log("Coin inserted. Select a product.");
      this.status = "has_coin";
    } else if (this.status === "has_coin") {
      console.log("Already has a coin. Returning extra.");
    } else if (this.status === "dispensing") {
      console.log("Dispensing in progress. Please wait.");
    } else if (this.status === "out_of_stock") {
      console.log("Out of stock. Returning coin.");
    }
    // Add a new state? Edit this method. And every other method.
  }

  selectProduct() {
    if (this.status === "idle") {
      console.log("Insert a coin first.");
    } else if (this.status === "has_coin") {
      if (this.stock === 0) {
        console.log("Out of stock.");
        this.status = "out_of_stock";
        return;
      }
      console.log("Product selected. Dispensing...");
      this.status = "dispensing";
    } else if (this.status === "dispensing") {
      console.log("Please wait.");
    } else if (this.status === "out_of_stock") {
      console.log("Out of stock.");
    }
    // The if/else block grows by one branch every time you add a state
  }

  // ... dispense() and refund() each have their own copy of the same if/else tree
}

[UNIQUE INSIGHT]: The status-field approach is not wrong for simple cases. If an object has two states and three methods, the if/else approach is fine — it is less code and easier to read. The State pattern introduces four or more classes for a problem that one class could solve. Use the pattern when the number of states times the number of methods makes the branching genuinely hard to follow, or when new states are being added frequently and you want to contain that growth.


Code Example 4 — State with a Shared Interface (Explicit Contract)

In JavaScript, interfaces are not enforced at runtime. But documenting the expected interface in a base class or JSDoc comment makes the contract explicit and catches missing method implementations during code review.

// Base class acts as an interface contract for all states
// Every concrete state must implement these four methods
class VendingMachineState {
  constructor(machine) {
    if (new.target === VendingMachineState) {
      throw new Error("VendingMachineState is abstract. Do not instantiate directly.");
    }
    this.machine = machine;
  }

  insertCoin() {
    throw new Error(`${this.constructor.name} must implement insertCoin()`);
  }

  selectProduct() {
    throw new Error(`${this.constructor.name} must implement selectProduct()`);
  }

  dispense() {
    throw new Error(`${this.constructor.name} must implement dispense()`);
  }

  refund() {
    throw new Error(`${this.constructor.name} must implement refund()`);
  }
}

// Concrete states extend the base — they inherit the guard and must override all methods
class ReadyState extends VendingMachineState {
  insertCoin() {
    console.log("Coin accepted.");
    this.machine.setState(this.machine.selectedState);
  }

  selectProduct() {
    console.log("Please insert a coin first.");
  }

  dispense() {
    console.log("Please insert a coin first.");
  }

  refund() {
    console.log("No coin to refund.");
  }
}

// Any state that forgets to implement a method will throw a clear error at runtime
// rather than silently failing or producing a wrong result

[INTERNAL-LINK: abstract classes and enforcing interfaces in JS → Lesson 3.2: Abstract Classes]


State vs Strategy — The Interview Trap

This comparison is the question most interviewers build around once they confirm you know both patterns. State and Strategy look nearly identical structurally. Both involve a context class that holds a reference to a pluggable object and delegates calls to it. The difference is in intent, ownership of transitions, and whether the delegate changes during the object's lifetime.

STATE vs STRATEGY — FULL COMPARISON
---------------------------------------------------------------------------
Feature              State Pattern               Strategy Pattern
---------------------------------------------------------------------------
Intent               An object changes its       An object selects one
                     behavior when its           algorithm from a family
                     internal state changes.     of algorithms.
                     "I am different things      "I will do this using
                     at different times."        this particular approach."

Who changes          The state objects           The client/caller
the current          themselves trigger          explicitly sets which
delegate?            transitions. The context    strategy to use. The
                     rarely does it directly.    context does not change
                                                 it on its own.

Does the             Yes. Transitions are        Not typically.
delegate change      part of the core            Strategy is usually set
during runtime?      behavior — that is the      once (or rarely changed)
                     whole point.                per operation.

States/Strategies    States know about each      Strategies are
aware of each        other (to trigger           independent. A strategy
other?               transitions).               has no idea what other
                                                 strategies exist.

State stored         Context holds current       Context holds current
where?               state. State may also       strategy. No transition
                     hold a reference back       mechanism inside the
                     to the context.             strategy itself.

Driving force        Internal events trigger     External code chooses
for change           transitions. The object     which algorithm to
                     manages its own             apply based on outside
                     lifecycle.                  conditions.

Typical example      Vending machine,            Sorting algorithm
                     traffic light,              selection, payment
                     order workflow,             method, compression
                     TCP connection              algorithm, route
                                                 planning

Core question        "What state am I in        "Which algorithm
it answers           right now, and what         should I use to
                     can I do?"                  accomplish this task?"

Structural           Context + State             Context + Strategy
components           interface + concrete        interface + concrete
                     state classes               strategy classes

Can they be          Yes — structurally          Yes — structurally
confused?            identical. The difference   identical. The difference
                     is always conceptual        is always conceptual
                     and in transition           and in who drives
                     ownership.                  the change.
---------------------------------------------------------------------------

[IMAGE: Two-column diagram side by side - left labeled "STATE" showing a context box with arrows between state objects labeled with transition triggers, right labeled "STRATEGY" showing a context box with one strategy slot and an external caller swapping the strategy in - search terms: "state pattern vs strategy pattern diagram OOP"]

The one-sentence summary interviewers want to hear:

State: The object knows what state it is in and changes its own delegate as events happen. Strategy: The caller chooses which algorithm the object will use for a task.


When Should You Use the State Pattern?

Use State when all of these are true:

  1. An object behaves differently depending on a current "mode" or "status."
  2. The number of states is more than two or three, or new states are added regularly.
  3. The branching logic (if/else or switch on a status field) is duplicated across multiple methods in the class.
  4. State transitions are triggered by the object's own behavior, not by the caller.

Do not use State when:

  • The object has only two modes and the branching is trivial.
  • The "state" is just a single boolean flag with no complex behavior per state.
  • Transitions are always driven externally by the caller (that is Strategy's territory).
  • The extra classes add complexity without reducing the actual branching burden.

[CHART: Decision flowchart - "Should I use the State pattern?" - starting from "Does behavior change based on an internal status?" branching through number of states, duplicated branching, and transition ownership to yes/no recommendation]


Common Mistakes

  • Putting transition logic in the context instead of the states. If the context class contains a large if/else block deciding which state to switch to next, you have moved the problem rather than solved it. Transition logic belongs inside each state class. The context only calls setState() when a state tells it to.

  • Confusing State with Strategy in the interview. The structures are visually identical. The question is always about who drives the change and why. In State, transitions are internal and automatic. In Strategy, the caller picks the algorithm. Practise the one-sentence version: "State changes itself; Strategy is chosen externally."

  • Creating state classes for things that are not states. Not every property variation is a state. A User with a role field (admin, editor, viewer) might look like a State pattern candidate, but if only one or two methods behave differently and the role rarely changes, a simple permission-check function is cleaner than four state classes.

  • Forgetting to initialize the context with a valid starting state. If the context's currentState is null at construction and a method is called immediately, you get a null reference error. Always assign a concrete initial state in the constructor. The VendingMachine example above initializes based on stock level — either idleState or outOfStockState.

  • Making states stateless when they need to remember something. A state class can hold its own data (duration, retry count, error message). Treating every state as a pure behavior delegate and then trying to store transition data in the context creates an awkward coupling. If a state needs data specific to that state, put it in the state class.


Interview Questions

Q: What problem does the State pattern solve?

The State pattern solves the "conditional explosion" problem. When an object behaves differently based on an internal status and you model that with if/else or switch statements, every method accumulates one branch per state. Adding a new state means editing every method. The State pattern extracts each state into its own class. Each class handles only its own behavior. Adding a new state means adding a new class, not editing existing ones. This satisfies the Open/Closed Principle.

Q: What is the structural difference between State and Strategy?

Structurally, they are nearly identical: a context class holds a reference to a pluggable object and delegates method calls to it. The difference is conceptual. In Strategy, the caller selects which algorithm the context uses, and the strategy object does not change itself. In State, the state objects trigger their own transitions: a state method tells the context to switch to the next state. The object manages its own lifecycle. Interviewers use this comparison to test whether you understand intent, not just structure.

Q: Who is responsible for triggering state transitions?

In the State pattern, the state objects themselves are responsible. When HasCoinState.selectProduct() is called, it calls this.machine.setState(this.machine.dispensingState) directly. The context class delegates the call and the state class decides what comes next. This is the key distinction from Strategy, where the context or caller makes that decision externally. Some implementations allow the context to manage transitions if the transition rules are simple enough, but the canonical pattern puts this responsibility in the states.

Q: Can the same state object be shared across multiple context instances?

Yes, if the state object holds no instance-specific data. A stateless state object (one that only holds a reference to the context it was constructed with) should not be shared across multiple contexts, because this.machine would point to only one of them. But if you design state objects as pure behavior delegates with no stored reference (passing the context as a method argument instead), they can be shared as flyweights. This is a memory optimization sometimes seen in high-volume state machines.

Q: How does the State pattern relate to SOLID principles?

The State pattern directly supports two SOLID principles. It supports the Open/Closed Principle: adding a new state requires adding a new class, not modifying existing state classes or the context. It supports the Single Responsibility Principle: each state class is responsible for the behavior of exactly one state, nothing more. The context class is responsible for holding and delegating to the current state, nothing more. The original status-field approach violates both: every method in the context is responsible for every state's behavior simultaneously.


Quick Reference Cheat Sheet

STATE PATTERN — QUICK REFERENCE
---------------------------------------------------------------------------
Intent         Let an object change its behavior when its internal
               state changes. Encapsulate each state as a class.

Structure      Context class           holds currentState reference
               State interface         defines the shared method contract
               Concrete state classes  implement behavior for one state
               Transitions             triggered by state methods calling
                                       context.setState(nextState)

Key roles
  Context      - holds a reference to the current state object
               - delegates all public calls to currentState
               - exposes setState() for state objects to call
               - creates all state instances (usually in constructor)

  State        - implements all methods defined in the state interface
               - holds a back-reference to the context (usually)
               - calls context.setState() to trigger transitions
               - knows about adjacent states (the ones it transitions to)

---------------------------------------------------------------------------
SKELETON (JavaScript)
---------------------------------------------------------------------------

class Context {
  constructor() {
    this.stateA = new StateA(this);
    this.stateB = new StateB(this);
    this.currentState = this.stateA;   // initial state
  }

  action() {
    this.currentState.action();        // delegate to current state
  }

  setState(newState) {
    this.currentState = newState;
  }
}

class StateA {
  constructor(ctx) { this.ctx = ctx; }

  action() {
    console.log("StateA behavior");
    this.ctx.setState(this.ctx.stateB); // transition
  }
}

class StateB {
  constructor(ctx) { this.ctx = ctx; }

  action() {
    console.log("StateB behavior");
    this.ctx.setState(this.ctx.stateA); // transition back
  }
}

---------------------------------------------------------------------------
STATE vs STRATEGY — ONE-LINE SUMMARY
---------------------------------------------------------------------------
State:      Object changes its own delegate as internal events happen.
Strategy:   Caller chooses which algorithm the object will use.

---------------------------------------------------------------------------
WHEN TO USE STATE
---------------------------------------------------------------------------
Use it when:
  - Object has 3+ distinct modes with different behavior per mode
  - The same if/else block appears in multiple methods
  - Transitions are triggered by the object's own behavior
  - New states are added frequently

Skip it when:
  - Only 2 states and behavior differences are trivial
  - The "state" is a simple boolean flag
  - Transitions are always set externally by the caller (use Strategy)
  - The added classes increase complexity without reducing branching

---------------------------------------------------------------------------
COMMON STATE MACHINE APPLICATIONS
---------------------------------------------------------------------------
  Order workflow:    Pending -> Confirmed -> Shipped -> Delivered -> Cancelled
  User session:      LoggedOut -> LoggingIn -> LoggedIn -> Locked
  Network socket:    Closed -> Listening -> Established -> Closing
  Media player:      Stopped -> Playing -> Paused -> Buffering
  Vending machine:   Idle -> HasCoin -> Dispensing -> OutOfStock
  Traffic light:     Red -> Green -> Yellow -> Red (cyclic)

---------------------------------------------------------------------------
STATE PATTERN vs STRATEGY PATTERN
---------------------------------------------------------------------------
Feature              State                     Strategy
Delegate changes?    Yes, automatically        Rarely, set externally
Who triggers change? State objects             Client / caller code
States aware of      Yes (know successors)     No (independent)
each other?
Lifecycle            Managed by the object     Managed by the caller
Core question        "What mode am I in?"      "Which algorithm do I use?"
---------------------------------------------------------------------------

State Pattern visual 1


Previous: Lesson 8.5 - Strategy Pattern Next: End of course


This is Lesson 8.6 of the OOP Interview Prep Course — 8 chapters, 41 lessons.

On this page