Skip to content

State

State lets an object alter its behaviour when its internal state changes. Each state becomes its own object, and the context delegates to whichever state it currently holds. From the outside the context appears to change class at runtime.

Consider an order that moves through draft, paid, shipped, and delivered. Each action — pay, ship, deliver, cancel — is allowed in some states and forbidden in others. The naive implementation is a status string and a thicket of conditionals in every method: “if status is draft do this, else if paid do that, else throw.” The rules for each status are smeared across every method, and adding a status means revisiting all of them.

The variation here is behaviour per state, and the transitions between states. State gathers all the behaviour for one status into a single object. The context holds a reference to the current state and forwards calls to it; each state knows what it allows and which state to move to next. Adding a status is adding a class, and the transition rules live next to the behaviour they govern.

classDiagram
  class Order {
    -state: OrderState
    +pay()
    +ship()
    +status() string
  }
  class OrderState {
    <<interface>>
    +pay(order)
    +ship(order)
    +name() string
  }
  class DraftState {
    +pay(order)
    +ship(order)
  }
  class PaidState {
    +pay(order)
    +ship(order)
  }
  Order o--> OrderState
  OrderState <|.. DraftState
  OrderState <|.. PaidState
An Order delegates to its current OrderState, which decides what is allowed and which state comes next
  • State — the interface declaring the actions that vary by state.
  • Concrete State — one state object; it implements the allowed actions and triggers transitions to other states.
  • Context — holds a reference to the current state and delegates every state-dependent call to it. It exposes a way for states to switch it to a new state.
  • Client — drives the context by calling its actions; it does not manipulate states directly.

An order state machine where each state decides what pay and ship do and which state comes next. Contrast with Strategy: there the client chooses the algorithm, whereas here the states choose their own successor.

interface OrderState {
name(): string;
pay(order: Order): void;
ship(order: Order): void;
}
class Order {
private state: OrderState = new DraftState();
setState(s: OrderState): void {
this.state = s;
}
status(): string {
return this.state.name();
}
pay(): void {
this.state.pay(this);
}
ship(): void {
this.state.ship(this);
}
}
class DraftState implements OrderState {
name(): string {
return 'draft';
}
pay(order: Order): void {
order.setState(new PaidState());
}
ship(_order: Order): void {
throw new Error('cannot ship an unpaid order');
}
}
class PaidState implements OrderState {
name(): string {
return 'paid';
}
pay(_order: Order): void {
throw new Error('already paid');
}
ship(order: Order): void {
order.setState(new ShippedState());
}
}
class ShippedState implements OrderState {
name(): string {
return 'shipped';
}
pay(_order: Order): void {
throw new Error('already paid');
}
ship(_order: Order): void {
throw new Error('already shipped');
}
}
const order = new Order();
order.pay();
order.ship();
console.log(order.status()); // "shipped"
  • Pro: replaces tangled per-status conditionals with one cohesive object per state.
  • Pro: transitions are explicit and live beside the behaviour they trigger, which makes the state machine easy to read.
  • Pro: adding a state means adding a class, leaving existing states untouched.
  • Con: more classes than a single status flag, which is overkill for two or three trivial states.
  • Con: transition logic is distributed across the states, so the full map of the machine is not in one place.
  • Strategy shares the same class diagram, but the intent diverges: a strategy is selected by the client to vary an algorithm, whereas a state object selects its own successor as the object’s condition changes. Strategy objects are usually unaware of each other; state objects know the states they transition to.
  • Singleton is sometimes used for stateless state objects so they can be shared rather than re-created on every transition.
What does the State pattern let an object do?
Where do the transition rules live in the State pattern?
How does State differ from Strategy in intent?
What replaces the tangled per-status conditionals?