State Pattern

Introduction

The State Pattern allows an object to alter its behavior when its internal state changes, making it appear as though the object has changed its class. Instead of scattering state-dependent logic across methods with conditionals, you encapsulate each state into its own class, and the object delegates behavior to its current state.

If you have ever seen a method riddled with if (status == "pending") checks scattered throughout a class, the State Pattern is the clean alternative.

The Problem

You are building an order management system. An order moves through several states: Pending, Processing, Shipped, and Delivered. Each state allows different actions — you can cancel a pending order but not a shipped one. You can ship a processing order but not a delivered one.

The naive approach is to add status checks in every method: cancel() checks if status is “pending,” ship() checks if status is “processing,” and so on. As states and transitions grow, the class becomes a tangled web of conditionals. Adding a new state (like “Returned”) means touching every method.

The Solution

The State Pattern creates a separate class for each state. Each state class implements the same interface and defines what actions are valid in that state. The order object (context) holds a reference to its current state and delegates all actions to it. State transitions happen by swapping the current state object.

This eliminates conditional logic and makes each state’s behavior self-contained. Adding a new state means adding a new class — no existing state classes need to change.

Key Principle

Encapsulate what varies. The thing that varies here is the behavior associated with each state. By extracting each state into its own class, you isolate state-specific logic and make transitions explicit and traceable.

Java Example

Scenario: An order management system with state-driven behavior.

// State interface
public interface OrderState {
    void next(OrderContext order);
    void cancel(OrderContext order);
    String getStatus();
}

// Context
public class OrderContext {
    private OrderState state;
    private final String orderId;

    public OrderContext(String orderId) {
        this.orderId = orderId;
        this.state = new PendingState();
        System.out.println("Order " + orderId + " created. Status: " + state.getStatus());
    }

    public void setState(OrderState state) {
        this.state = state;
        System.out.println("Order " + orderId + " status: " + state.getStatus());
    }

    public void next() {
        state.next(this);
    }

    public void cancel() {
        state.cancel(this);
    }

    public String getStatus() {
        return state.getStatus();
    }

    public String getOrderId() {
        return orderId;
    }
}

// Concrete State: Pending
public class PendingState implements OrderState {
    @Override
    public void next(OrderContext order) {
        System.out.println("Processing payment for order " + order.getOrderId() + "...");
        order.setState(new ProcessingState());
    }

    @Override
    public void cancel(OrderContext order) {
        System.out.println("Order " + order.getOrderId() + " cancelled.");
        order.setState(new CancelledState());
    }

    @Override
    public String getStatus() {
        return "PENDING";
    }
}

// Concrete State: Processing
public class ProcessingState implements OrderState {
    @Override
    public void next(OrderContext order) {
        System.out.println("Order " + order.getOrderId() + " shipped!");
        order.setState(new ShippedState());
    }

    @Override
    public void cancel(OrderContext order) {
        System.out.println("Order " + order.getOrderId()
            + " is being processed. Initiating cancellation...");
        order.setState(new CancelledState());
    }

    @Override
    public String getStatus() {
        return "PROCESSING";
    }
}

// Concrete State: Shipped
public class ShippedState implements OrderState {
    @Override
    public void next(OrderContext order) {
        System.out.println("Order " + order.getOrderId() + " delivered.");
        order.setState(new DeliveredState());
    }

    @Override
    public void cancel(OrderContext order) {
        System.out.println("Cannot cancel order " + order.getOrderId()
            + " — already shipped.");
    }

    @Override
    public String getStatus() {
        return "SHIPPED";
    }
}

// Concrete State: Delivered
public class DeliveredState implements OrderState {
    @Override
    public void next(OrderContext order) {
        System.out.println("Order " + order.getOrderId()
            + " already delivered. No further transitions.");
    }

    @Override
    public void cancel(OrderContext order) {
        System.out.println("Cannot cancel order " + order.getOrderId()
            + " — already delivered.");
    }

    @Override
    public String getStatus() {
        return "DELIVERED";
    }
}

// Concrete State: Cancelled
public class CancelledState implements OrderState {
    @Override
    public void next(OrderContext order) {
        System.out.println("Order " + order.getOrderId()
            + " is cancelled. Cannot proceed.");
    }

    @Override
    public void cancel(OrderContext order) {
        System.out.println("Order " + order.getOrderId()
            + " is already cancelled.");
    }

    @Override
    public String getStatus() {
        return "CANCELLED";
    }
}

// Usage
public class Main {
    public static void main(String[] args) {
        OrderContext order = new OrderContext("ORD-2001");

        order.next();      // Pending -> Processing
        order.next();      // Processing -> Shipped
        order.cancel();    // Cannot cancel — already shipped
        order.next();      // Shipped -> Delivered
        order.next();      // Already delivered

        System.out.println("\n--- Cancellation flow ---");
        OrderContext order2 = new OrderContext("ORD-2002");
        order2.cancel();   // Pending -> Cancelled
        order2.next();     // Cannot proceed — cancelled
    }
}

Python Example

Same order management system in Python.

from abc import ABC, abstractmethod


# State interface
class OrderState(ABC):
    @abstractmethod
    def next(self, order: "OrderContext") -> None:
        pass

    @abstractmethod
    def cancel(self, order: "OrderContext") -> None:
        pass

    @abstractmethod
    def get_status(self) -> str:
        pass


# Context
class OrderContext:
    def __init__(self, order_id: str):
        self.order_id = order_id
        self._state: OrderState = PendingState()
        print(f"Order {order_id} created. Status: {self._state.get_status()}")

    def set_state(self, state: OrderState) -> None:
        self._state = state
        print(f"Order {self.order_id} status: {self._state.get_status()}")

    def next(self) -> None:
        self._state.next(self)

    def cancel(self) -> None:
        self._state.cancel(self)

    @property
    def status(self) -> str:
        return self._state.get_status()


# Concrete State: Pending
class PendingState(OrderState):
    def next(self, order: OrderContext) -> None:
        print(f"Processing payment for order {order.order_id}...")
        order.set_state(ProcessingState())

    def cancel(self, order: OrderContext) -> None:
        print(f"Order {order.order_id} cancelled.")
        order.set_state(CancelledState())

    def get_status(self) -> str:
        return "PENDING"


# Concrete State: Processing
class ProcessingState(OrderState):
    def next(self, order: OrderContext) -> None:
        print(f"Order {order.order_id} shipped!")
        order.set_state(ShippedState())

    def cancel(self, order: OrderContext) -> None:
        print(f"Order {order.order_id} is being processed. "
              f"Initiating cancellation...")
        order.set_state(CancelledState())

    def get_status(self) -> str:
        return "PROCESSING"


# Concrete State: Shipped
class ShippedState(OrderState):
    def next(self, order: OrderContext) -> None:
        print(f"Order {order.order_id} delivered.")
        order.set_state(DeliveredState())

    def cancel(self, order: OrderContext) -> None:
        print(f"Cannot cancel order {order.order_id} — already shipped.")

    def get_status(self) -> str:
        return "SHIPPED"


# Concrete State: Delivered
class DeliveredState(OrderState):
    def next(self, order: OrderContext) -> None:
        print(f"Order {order.order_id} already delivered. "
              f"No further transitions.")

    def cancel(self, order: OrderContext) -> None:
        print(f"Cannot cancel order {order.order_id} — already delivered.")

    def get_status(self) -> str:
        return "DELIVERED"


# Concrete State: Cancelled
class CancelledState(OrderState):
    def next(self, order: OrderContext) -> None:
        print(f"Order {order.order_id} is cancelled. Cannot proceed.")

    def cancel(self, order: OrderContext) -> None:
        print(f"Order {order.order_id} is already cancelled.")

    def get_status(self) -> str:
        return "CANCELLED"


# Usage
if __name__ == "__main__":
    order = OrderContext("ORD-2001")

    order.next()      # Pending -> Processing
    order.next()      # Processing -> Shipped
    order.cancel()    # Cannot cancel — already shipped
    order.next()      # Shipped -> Delivered
    order.next()      # Already delivered

    print("\n--- Cancellation flow ---")
    order2 = OrderContext("ORD-2002")
    order2.cancel()   # Pending -> Cancelled
    order2.next()     # Cannot proceed — cancelled

When to Use

  • Objects with distinct behavioral states — When an object behaves differently depending on its current state, and transitions between states are well-defined (orders, workflows, game characters).
  • Eliminating state conditionals — When methods are cluttered with if/else or switch statements that check the current state before acting.
  • State machines — When modeling finite state machines where transitions and allowed actions are state-dependent.
  • Workflow engines — When documents, tickets, or requests move through approval stages with different rules at each stage.

Real-World Usage

  • TCP Connection — A TCP socket transitions through states (Listening, Established, Closed) with different behaviors in each.
  • Java Thread States — The Thread class has states (NEW, RUNNABLE, BLOCKED, TERMINATED) that determine allowed operations.
  • Spring State Machine — The spring-statemachine project provides a framework for building state-based applications using this pattern.
  • Workflow tools (Jira, GitHub Issues) — Issue status transitions (Open, In Progress, Done) follow the state pattern with rules about valid transitions.



Subscribe To Our Newsletter
You will receive our latest post and tutorial.
Thank you for subscribing!

required
required


Leave a Reply

Your email address will not be published. Required fields are marked *