SSourav Saha
HomeExperienceSoftware DesignSystem DesignLearningBooksToolsContact
SSourav Saha

Building scalable backend systems, distributed infrastructure, and cloud-native applications.

Navigation

  • Home
  • Experience
  • Software Design
  • System Design
  • Learning

More

  • Books
  • Tools
  • Contact

Connect

  • LinkedIn
  • Email

© 2026 Sourav Saha. All rights reserved.

Built with using Next.js

Software DesignState Pattern
Software DesignBehavioral Patterns

State Pattern

Allow an object to alter its behavior when its internal state changes — appearing as if it changed its class.

August 28, 20245 min read
design-patternsjavastatebehavioral

One-line Definition

Extracts state-specific behaviors into separate classes, allowing an object to change its behavior at runtime as its internal state changes.


Problem

Suppose you are programming a Vending Machine. It behaves wildly differently depending on whether it's in the Idle, HasCoin, or Dispensing state. If you try to manage this using a single VendingMachine class, every single method (insertCoin(), pressButton(), dispense()) requires a massive switch statement checking the current state. Adding a new state (e.g., OutOfStock) breaks every method. The State Pattern solves this by turning states into objects, delegating all state-specific logic to the currently active state object.


Real-world Analogy

Think of a smartphone. If the phone is locked (State A), pressing the home button wakes up the screen. If the phone is unlocked (State B), pressing the home button goes to the home screen. If an app is open (State C), pressing the home button minimizes the app. The physical button is exactly the same, but its behavior completely changes based on the phone's current state.


Structure

  • Client
    • Interacts with the Context, largely unaware of the internal state transitions.
  • Context
    • Maintains a reference to one of the ConcreteState subclasses that represents the current state. Delegates all work to this state object.
  • State
    • Interface declaring state-specific methods.
  • ConcreteState
    • Implements the behavior associated with a specific state of the Context. Can optionally trigger transitions to other states.

Diagram

classDiagram
    class Client {
    }
    class Context {
        -State state
        +request()
        +setState(State)
    }
    class State {
        <<interface>>
        +handle(Context)
    }
    class ConcreteStateA {
        +handle(Context)
    }
    class ConcreteStateB {
        +handle(Context)
    }
    
    Client --> Context
    Context o-- State : delegates
    ConcreteStateA ..|> State
    ConcreteStateB ..|> State
    ConcreteStateA --> Context : triggers transition

Code Walkthrough

Notice how the VendingMachine (Context) delegates insertCoin() to whatever State object it is currently holding. The IdleState actually triggers the transition into the HasCoinState.

interface State {
    void insertCoin(VendingMachine machine);
    void dispense(VendingMachine machine);
}

class VendingMachine {
    private State state;

    public VendingMachine() {
        this.state = new IdleState(); // Initial state
    }

    public void setState(State state) {
        this.state = state;
    }

    // Delegation!
    public void insertCoin() { state.insertCoin(this); }
    public void dispense() { state.dispense(this); }
}

class IdleState implements State {
    @Override
    public void insertCoin(VendingMachine machine) {
        System.out.println("Coin inserted. Ready to dispense.");
        machine.setState(new HasCoinState()); // Transition!
    }

    @Override
    public void dispense(VendingMachine machine) {
        System.out.println("Insert coin first!");
    }
}

class HasCoinState implements State {
    @Override
    public void insertCoin(VendingMachine machine) {
        System.out.println("Already has a coin. Please wait.");
    }

    @Override
    public void dispense(VendingMachine machine) {
        System.out.println("Dispensing item...");
        machine.setState(new IdleState()); // Transition!
    }
}

class Main {
    public static void main(String[] args) {
        VendingMachine machine = new VendingMachine();

        machine.dispense();   // Fails: in IdleState
        machine.insertCoin(); // Success: moves to HasCoinState
        machine.insertCoin(); // Fails: already in HasCoinState
        machine.dispense();   // Success: dispenses and goes back to IdleState
    }
}

Bad vs Good

Bad Approach

Problems

  • Massive, duplicated if-else blocks in every single method.
  • Adding a new state (e.g., MaintenanceMode) requires touching every method, violating the Open/Closed Principle.
class VendingMachine {
    private String state = "IDLE";

    public void insertCoin() {
        if (state.equals("IDLE")) {
            state = "HAS_COIN";
        } else if (state.equals("HAS_COIN")) {
            System.out.println("Already has coin.");
        }
    }

    public void dispense() {
        if (state.equals("IDLE")) {
            System.out.println("Insert coin first.");
        } else if (state.equals("HAS_COIN")) {
            state = "IDLE";
        }
    }
}

Better Approach

Improvements

  • No if-else blocks. The behavior is inherently determined by polymorphism.
  • Adding a new state means creating one new class, completely isolated from existing logic.
// Context delegates entirely to the current State object
public void insertCoin() { 
    state.insertCoin(this); 
}

Pros vs Cons

ProsCons
Eliminates massive, monolithic state machines full of if-elseCan be overkill for a state machine with only two or three states
Strongly adheres to the Single Responsibility PrincipleScatters state transition logic across many classes
Enforces the Open/Closed Principle when adding new statesCan create tight coupling between Concrete States if they manage transitions directly
Makes state transitions explicit rather than relying on raw strings/enums

When to Use

  • An object's behavior changes drastically depending on its state.
  • You have massive conditional statements (switch/if) that check the same state variable across numerous methods.
  • You are implementing a formal Finite State Machine (FSM).

When Not to Use

  • The state machine is extremely simple (e.g., just ON / OFF) and will never change.
  • The behavior doesn't change significantly based on the state.

Real-world Examples

  • TCP Connection states (LISTEN, ESTABLISHED, CLOSED).
  • Document workflows (DRAFT, REVIEW, PUBLISHED).
  • JSF (JavaServer Faces) lifecycle phases.

Key Takeaway

The State Pattern replaces giant switch statements with polymorphism, turning each state into its own class. Use it when an object behaves like a Finite State Machine, altering its entire personality based on internal conditions. Be aware, however, that it scatters your transition logic across multiple files, which can make the overall "flow" harder to read at a glance.

PreviousMediator PatternNextAdapter Pattern