State
An order that's already Delivered shouldn't be cancellable the same way a
Pending order is — the same method call, cancel(), needs to mean
something completely different depending on what state the order is currently in. The
State pattern handles this by giving each state its own class
that implements the shared behavior its own way, and letting the object simply swap which
state object it's currently delegating to.
⚠️ The Problem
Picture an Order class with a status field that can be
Pending, Paid, Shipped, Delivered, or
Cancelled. Every method — pay(), ship(),
cancel(), deliver() — needs a big switch or
if/else chain on status to decide what's even allowed: you can't ship
a Pending order, you can't cancel a Delivered one, and paying twice
should probably be an error. As more statuses and rules get added, every one of those methods
grows another branch, the branches have to stay in sync with each other across the whole class,
and it becomes very easy to forget an edge case buried in the fifth else if down.
✅ The Solution
Give each status its own class — PendingState, PaidState,
ShippedState, and so on — all implementing a common OrderState
interface with methods like pay(), ship(), and cancel().
The Order object holds a reference to its current state object and simply forwards
every call to it: order.cancel() becomes
this.state.cancel(this). Each state class only has to know its own valid
transitions — PendingState.cancel() moves the order to CancelledState,
while DeliveredState.cancel() might just throw or no-op. Transitioning states is
just reassigning which state object Order points to next.
Example 1 — an order status state machine
interface OrderState {
pay(order: Order): void;
ship(order: Order): void;
cancel(order: Order): void;
name: string;
}
class PendingState implements OrderState {
name = "Pending";
pay(order: Order) { order.setState(new PaidState()); }
ship(order: Order) { console.log("Can't ship — payment not received yet."); }
cancel(order: Order) { order.setState(new CancelledState()); }
}
class PaidState implements OrderState {
name = "Paid";
pay() { console.log("Already paid."); }
ship(order: Order) { order.setState(new ShippedState()); }
cancel(order: Order) { order.setState(new CancelledState()); }
}
class ShippedState implements OrderState {
name = "Shipped";
pay() { console.log("Already paid."); }
ship() { console.log("Already shipped."); }
cancel() { console.log("Too late — order has already shipped."); }
}
class CancelledState implements OrderState {
name = "Cancelled";
pay() { console.log("Order is cancelled."); }
ship() { console.log("Order is cancelled."); }
cancel() { console.log("Already cancelled."); }
}
class Order {
private state: OrderState = new PendingState();
setState(state: OrderState) { this.state = state; }
pay() { this.state.pay(this); }
ship() { this.state.ship(this); }
cancel() { this.state.cancel(this); }
get status() { return this.state.name; }
}
const order = new Order();
order.ship(); // "Can't ship — payment not received yet."
order.pay();
console.log(order.status); // "Paid"
order.ship();
console.log(order.status); // "Shipped"
order.cancel(); // "Too late — order has already shipped."
Notice Order itself has no if statements about status at all — it
just forwards every call to whatever OrderState object it currently holds. Adding a
ReturnedState later means writing one new class, not touching every method on
Order.
Example 2 — a secondary context: a media player
class PlayerState:
def play(self, player): raise NotImplementedError
def pause(self, player): raise NotImplementedError
def stop(self, player): raise NotImplementedError
class PlayingState(PlayerState):
def play(self, player):
print("Already playing.")
def pause(self, player):
print("Pausing playback.")
player.state = PausedState()
def stop(self, player):
print("Stopping playback.")
player.state = StoppedState()
class PausedState(PlayerState):
def play(self, player):
print("Resuming playback.")
player.state = PlayingState()
def pause(self, player):
print("Already paused.")
def stop(self, player):
print("Stopping from pause.")
player.state = StoppedState()
class StoppedState(PlayerState):
def play(self, player):
print("Starting playback from the beginning.")
player.state = PlayingState()
def pause(self, player):
print("Nothing to pause — playback is stopped.")
def stop(self, player):
print("Already stopped.")
class MediaPlayer:
def __init__(self):
self.state = StoppedState()
def play(self): self.state.play(self)
def pause(self): self.state.pause(self)
def stop(self): self.state.stop(self)
player = MediaPlayer()
player.play() # Starting playback from the beginning.
player.pause() # Pausing playback.
player.pause() # Already paused.
player.play() # Resuming playback.
Same shape as the order example: MediaPlayer never checks "what state am I in"
with an if — it just calls the method on whichever PlayerState object
it's currently holding, and that object decides what happens next, including what it transitions
to.
👍 When To Use It
- An object's behavior needs to change substantially based on an internal state, and that state changes at runtime through a well-defined set of transitions.
- You keep finding the same
status/modefield checked withif/elseorswitchacross many methods of the same class. - New states get added over time, and you want adding one to mean "add a class," not "edit every existing method."
👎 When NOT To Use It
- When there are only two simple states and the behavior difference is trivial — a boolean
flag and a single
ifis easier to read than two classes and an interface. - When the states rarely or never change after the object is created — State earns its keep specifically because of runtime transitions between many behaviors.
- When the "states" don't actually share a common interface of operations — if each one
needs a wildly different set of methods, forcing them into one
Stateinterface can be more awkward than the switch statement it replaced.
❓ FAQ
Isn't this exactly the same as the Strategy pattern?
Structurally, yes — both give you an interface with interchangeable implementations that
a context object delegates to, and the code often looks nearly identical. The difference is
in intent and where the decision comes from. State is about an object's
behavior changing as its own internal state evolves over time — the state objects
often know about and trigger transitions to each other (like PendingState.pay()
switching to PaidState), and the context rarely, if ever, gets told which state
to use from outside. Strategy is about a client choosing which interchangeable
algorithm to use — the strategy is typically passed in from outside by whoever constructs or
configures the context, has no notion of "the next strategy," and doesn't represent a point
in the object's own lifecycle. If the implementations know about and switch to each other, it's
State; if the caller picks one and hands it in, it's Strategy.
Who decides which state to transition to — the Order, or the State object?
In the classic version of the pattern, the current State object itself decides and calls
order.setState(...), exactly as in the example above — the context class stays
completely ignorant of the transition rules. Some implementations instead centralize the
transition table in the context for easier auditing of "what can go to what," trading a bit
of that decentralization for a single place to read all the rules. Both are valid; the
example here uses the more common, fully decentralized version.
Do I need a separate class for every single state, even trivial ones?
Not necessarily — if a handful of states share almost all their behavior, a shared base
class with template-style hooks, or even flyweight-shared stateless instances (since
PendingState above holds no per-order data, one shared instance could serve every
Order), can cut down on allocations and boilerplate. The core requirement is just
that each distinct behavior set lives in its own place, not that every state needs a bespoke
from-scratch class.
🙋 There Are No Dumb Questions
Q: Doesn't creating a new state object on every transition waste memory?
A: For most applications, no — state objects are usually tiny and short-lived, and creating one
per transition is negligible compared to the readability win. If profiling shows it actually
matters (very high-frequency transitions, e.g. a game running at 60fps), stateless state
objects can be pre-created once and shared as singletons/flyweights across every context that
needs them, since they hold no per-instance data of their own.
✅ Quick Check
In the State pattern, what typically decides which state to transition to next?
Next up: what if you need several interchangeable ways to perform the same task — like different pricing or routing algorithms — without a giant if/else picking between them? That's the Strategy pattern, and it looks a lot like State on the surface but solves a different problem.