On this page
Description
State is the behavioral pattern for objects whose behavior depends on what stage they're in - an order that can be paid but not shipped yet, a connection that can send only while open. Instead of checking a status field in every method, each state becomes its own class that knows how to handle every action and which state comes next. This skill covers the pattern, how to build a state machine with it, and the alternatives for small machines.
An order moves through statuses: NEW, PAID, SHIPPED, DELIVERED, CANCELLED. The first version keeps a status field and checks it everywhere:
Java
void ship() {
if (status == NEW) throw new IllegalStateException("not paid yet");
else if (status == PAID) { status = SHIPPED; notifyWarehouse(); }
else if (status == SHIPPED || status == DELIVERED) throw new IllegalStateException("already shipped");
else if (status == CANCELLED) throw new IllegalStateException("cancelled");
}
// ...and the same kind of switch in pay(), cancel(), deliver(), refund()...
Python
def ship(self) -> None:
if self.status == Status.NEW:
raise RuntimeError("not paid yet")
elif self.status == Status.PAID:
self.status = Status.SHIPPED
self._notify_warehouse()
elif self.status in (Status.SHIPPED, Status.DELIVERED):
raise RuntimeError("already shipped")
elif self.status == Status.CANCELLED:
raise RuntimeError("cancelled")
# ...and the same kind of chain in pay(), cancel(), deliver(), refund()...
The rules for one status are spread across every method, and each new status means editing all of them.
We'll go from simple to more technical:
- The pattern: a context that delegates to its current state object.
- Building a state machine, step by step.
- Where you'll see state machines: orders, workflows, connections, games.
- Keeping the machine visible and correct - and State versus Strategy.
The pattern
State gives each status its own class. The object (the context) delegates every action to its current state object, and the state decides what happens - including which state comes next.
Java
interface OrderState {
OrderState pay(Order o);
OrderState ship(Order o);
OrderState cancel(Order o);
}
class PaidState implements OrderState {
public OrderState pay(Order o) { throw new IllegalStateException("already paid"); }
public OrderState ship(Order o) { o.notifyWarehouse(); return new ShippedState(); }
public OrderState cancel(Order o) { o.refund(); return new CancelledState(); }
}
class Order {
private OrderState state = new NewState();
void pay() { state = state.pay(this); }
void ship() { state = state.ship(this); }
void cancel() { state = state.cancel(this); }
}
Python
from abc import ABC, abstractmethod
class OrderState(ABC):
@abstractmethod
def pay(self, o: Order) -> "OrderState": ...
@abstractmethod
def ship(self, o: Order) -> "OrderState": ...
@abstractmethod
def cancel(self, o: Order) -> "OrderState": ...
class PaidState(OrderState):
def pay(self, o: Order) -> OrderState:
raise RuntimeError("already paid")
def ship(self, o: Order) -> OrderState:
o.notify_warehouse()
return ShippedState()
def cancel(self, o: Order) -> OrderState:
o.refund()
return CancelledState()
class Order:
def __init__(self):
self._state: OrderState = NewState()
def pay(self) -> None:
self._state = self._state.pay(self)
def ship(self) -> None:
self._state = self._state.ship(self)
def cancel(self) -> None:
self._state = self._state.cancel(self)
Everything about "being paid" now lives in PaidState.
How it works, step by step
- List the states and the events (actions) that can happen.
- Create a state interface with one method per event.
- Write one class per state; each method either performs the action and returns the next state, or rejects it.
- The context holds the current state and delegates each event to it, replacing the state with whatever comes back.
- Adding a state means adding one class (and updating the states that lead into it).
Where you'll see it
- Order and payment lifecycles, document workflows (draft, in review, published), ticket systems.
- Network connections (connecting, open, closing, closed) and media players (playing, paused, stopped).
- Game characters (idle, running, jumping) and vending machines.
- Libraries such as Spring Statemachine formalize the same idea.
Choosing among the behavioral patterns
| If you need to... | Reach for | It's about |
|---|---|---|
| Let many objects react when something happens, without the source knowing them | Observer | Who gets told about a change. |
| Swap one algorithm for another at runtime | Strategy | How a task is done - chosen from outside. |
| Turn a request into an object you can queue, log, undo or redo | Command | What to do, packaged for later. |
| Make an object behave differently depending on its current state | State (this lesson) | Behavior that changes itself as the object moves between states. |
| Fix the steps of an algorithm but let subclasses fill in some of them | Template Method | The skeleton stays, the details vary. |
Common mistakes
| Mistake | What happens | Do this instead |
|---|---|---|
A status switch in every method | each new status edits every method | one class (or table entry) per state |
| Silently ignoring invalid events | bugs hide; orders get stuck | reject them explicitly |
| One state per combination of independent aspects | dozens of classes (paid-and-wrapped-and-...) | separate machines for independent aspects |
| Transitions scattered so nobody sees the lifecycle | reviews and audits are guesswork | a transition table or a generated diagram |
| Saving state objects as-is | can't reload them | persist the state's name (and its data) |
Check yourself
Try to answer before opening each one - recalling beats re-reading.
1. In the State pattern, what does each state class decide?
Show answer
How to handle every event while the object is in that state - including which state comes next, or that the event is rejected.
2. An order in NEW receives ship(). What should happen?
Show answer
Reject it explicitly (an error or a clear result) and stay in NEW. Silently ignoring it hides bugs; shipping an unpaid order breaks the business rules.
3. When is an enum or a transition table better than one class per state?
Show answer
When states mostly just decide the next state: the whole lifecycle then sits in one readable place. Per-state classes win when each state carries a lot of behavior.
Key takeaways
- Each state is a class that knows how to handle every event - and what state comes next.
- The context delegates to its current state instead of switching on a status field.
- Reject invalid transitions clearly; never ignore them silently.
- For small machines, an enum or a transition table keeps the whole lifecycle visible.
- The object changes its own state (State); the client picks the algorithm (Strategy).