Back to Design Patterns
Behavioral Pattern

State Pattern

Allow an object to alter its behaviour when its internal state changes. The object will appear to change its class.

Intermediate11 min read

What is the State Pattern?

The State pattern is a behavioral design pattern that lets an object change its behaviour when its internal state changes. Instead of giant if/else or switch chains, each possible state becomes its own class with its own behaviour — and the object delegates actions to the current state object.

Think of a traffic light: the same "next" action does completely different things depending on whether the light is currently red, yellow, or green. The light doesn't check its own colour with if/else — it delegates to the current state, which knows exactly what to do and what comes next.

The pattern is essentially a finite state machine (FSM) implemented with objects. Every state knows which transitions are valid and what the next state should be — keeping all state logic encapsulated and isolated.

Key Idea: Replace conditional state checks with polymorphism. Each state is a class; the context delegates to the current state object.

Core Participants

Context

Context

The main object whose behaviour changes. It holds a reference to the current state object and delegates all state-dependent operations to it. It also exposes setState() so states can trigger transitions.

State Interface

State Interface

Declares all the methods that every state must implement. This ensures the context can call any method on any state without knowing which concrete state it is.

Concrete State

Concrete State

Implements the State interface for one specific state. Each method either performs the appropriate action for that state, or logs an error/does nothing if the action isn't valid in this state. States can also trigger transitions by calling context.setState().

!Problem It Solves

Massive Conditional Bloat

Without State, every method in the context must check the current state and branch accordingly — leading to:

  • → Giant if/else or switch blocks duplicated in every method
  • → Adding a new state means editing every single method
  • → Transitions are scattered throughout the code
  • → Impossible to see the full state machine at a glance

Without State Pattern

// Conditional explosion — hard to maintain
void pay(Order order) {
    if (order.status == PENDING) {
        // handle payment
    } else if (order.status == PAID) {
        // already paid
    } else if (order.status == SHIPPED) {
        // error
    } else if (order.status == CANCELLED) {
        // error
    }
    // Same if/else repeated in ship(), deliver(), cancel()...
}

With State Pattern

// Each state handles its own logic — no conditionals
class PendingState {
    void pay(Order o)    { o.setState(new PaidState()); }
    void ship(Order o)   { System.out.println("Not paid yet"); }
    void cancel(Order o) { o.setState(new CancelledState()); }
}
// Add a new state → add a new class, nothing else changes

State Diagram — Order Lifecycle

Pending
pay()
Paid
ship()
Shipped
deliver()
Delivered
cancel() from Pending or Paid → Cancelled

Implementation

// State Pattern — Vending Machine

// ─── State interface ──────────────────────────────────────────
interface VendingMachineState {
    void insertCoin(VendingMachine machine);
    void selectProduct(VendingMachine machine, String product);
    void dispense(VendingMachine machine);
    void cancel(VendingMachine machine);
}

// ─── Context ──────────────────────────────────────────────────
class VendingMachine {
    private VendingMachineState state;
    private double balance;
    private String selectedProduct;

    public VendingMachine() {
        this.state = new IdleState();  // start in Idle
        this.balance = 0;
    }

    // Delegate all actions to the current state
    public void insertCoin(double amount) {
        balance += amount;
        state.insertCoin(this);
    }
    public void selectProduct(String product) { state.selectProduct(this, product); }
    public void dispense()                    { state.dispense(this); }
    public void cancel()                      { state.cancel(this); }

    // State transitions
    public void setState(VendingMachineState state) {
        System.out.println("[Machine] State → " + state.getClass().getSimpleName());
        this.state = state;
    }

    public double  getBalance()         { return balance; }
    public void    setBalance(double b) { this.balance = b; }
    public String  getSelected()        { return selectedProduct; }
    public void    setSelected(String p){ this.selectedProduct = p; }
}

// ─── Concrete States ──────────────────────────────────────────
class IdleState implements VendingMachineState {
    @Override
    public void insertCoin(VendingMachine m) {
        System.out.println("[Idle] Coin inserted. Balance: ₹" + m.getBalance());
        m.setState(new HasCoinState());
    }
    @Override public void selectProduct(VendingMachine m, String p) {
        System.out.println("[Idle] Please insert a coin first.");
    }
    @Override public void dispense(VendingMachine m) {
        System.out.println("[Idle] No coin inserted.");
    }
    @Override public void cancel(VendingMachine m) {
        System.out.println("[Idle] Nothing to cancel.");
    }
}

class HasCoinState implements VendingMachineState {
    @Override
    public void insertCoin(VendingMachine m) {
        System.out.println("[HasCoin] Additional coin. Balance: ₹" + m.getBalance());
    }
    @Override
    public void selectProduct(VendingMachine m, String product) {
        m.setSelected(product);
        System.out.println("[HasCoin] Selected: " + product);
        m.setState(new ProductSelectedState());
    }
    @Override public void dispense(VendingMachine m) {
        System.out.println("[HasCoin] Please select a product first.");
    }
    @Override
    public void cancel(VendingMachine m) {
        System.out.println("[HasCoin] Cancelled. Returning ₹" + m.getBalance());
        m.setBalance(0);
        m.setState(new IdleState());
    }
}

class ProductSelectedState implements VendingMachineState {
    @Override public void insertCoin(VendingMachine m) {
        System.out.println("[Selected] Product already selected. Press dispense.");
    }
    @Override public void selectProduct(VendingMachine m, String p) {
        System.out.println("[Selected] Already selected: " + m.getSelected());
    }
    @Override
    public void dispense(VendingMachine m) {
        System.out.println("[Selected] Dispensing: " + m.getSelected());
        m.setBalance(0);
        m.setState(new IdleState());
    }
    @Override
    public void cancel(VendingMachine m) {
        System.out.println("[Selected] Cancelled. Returning ₹" + m.getBalance());
        m.setBalance(0);
        m.setState(new IdleState());
    }
}

// ─── Client ───────────────────────────────────────────────────
public class Application {
    public static void main(String[] args) {
        VendingMachine machine = new VendingMachine();

        machine.selectProduct("Chips");     // Idle: insert coin first
        machine.insertCoin(20);             // → HasCoin state
        machine.insertCoin(10);             // add more
        machine.selectProduct("Chips");     // → ProductSelected state
        machine.dispense();                 // → back to Idle
        machine.cancel();                   // Idle: nothing to cancel
    }
}

When to Use State

Order / Workflow Lifecycles

Any entity that moves through defined stages — pending, paid, shipped, delivered, cancelled.

OrderState, InvoiceState, TicketState

UI Components with Modes

Buttons, toggles, forms that behave differently depending on their current mode — idle, loading, error, success.

ButtonState, FormState, PlayerState

Game Characters / AI

Game entities that change behaviour based on health, mode, or environment — idle, patrolling, attacking, fleeing.

EnemyIdleState, AttackState, FleeState

Network Connection States

Connections that move through connecting, connected, reconnecting, disconnected states with different behaviours in each.

ConnectingState, ConnectedState, DisconnectedState

Vending Machines / ATMs

Physical devices with a strict sequence of states — idle, has coin, product selected, dispensing.

IdleState, HasCoinState, DispensingState

Pros & Cons

Advantages

  • Eliminates large conditional blocks — Single Responsibility per state
  • Adding a new state = new class, nothing else changes (Open/Closed)
  • State transitions are explicit and easy to trace
  • Each state is isolated — easy to test individually
  • State diagram maps 1:1 to the code structure

Disadvantages

  • Many classes for systems with lots of states
  • Overkill for simple 2-3 state systems
  • States can become coupled if they directly reference each other
  • Requires discipline to keep transitions inside state classes

State vs Strategy — Common Confusion

AspectStateStrategy
IntentObject changes behaviour as its state evolvesClient selects an algorithm to use
TransitionsStates trigger each other (state machine)Client chooses the strategy explicitly
States aware of each other?Yes — states know about valid transitionsNo — strategies are independent
Context knowledgeStates reference the context to trigger transitionsStrategies typically don't need context reference

Real-World Examples

React's useState Hook

React manages UI state transitions — loading, success, error — using state variables. Libraries like XState bring the full State pattern to frontend, defining machines with explicit states and transitions.

TCP Connection (Network)

TCP implements a classic state machine: LISTEN, SYN_SENT, ESTABLISHED, FIN_WAIT, CLOSED. Each state handles the same network events differently, following the State pattern exactly.

Vending Machines & ATMs

ATM software is a textbook State pattern implementation — idle, card inserted, PIN entered, transaction, dispensing cash. Each physical state maps to a concrete state class.

Spring's Order FSM / Workflow Engines

Spring Statemachine and tools like Activiti/Camunda implement workflow engines using the State pattern — each workflow node is a state, transitions are triggered by events.

What's Next?

Now that you understand State, explore related Behavioral patterns: