State
State is a behavioral pattern that lets an object switch its entire behavior at runtime by delegating to one of several interchangeable state objects, so from the outside it looks as though the object's class changed.
Problem
A traffic light controller has to behave completely differently depending on which color is lit: Red means stop and wait, Yellow means caution and get ready to switch, Green means go. Each color also has its own duration and its own idea of what the next color should be.
The natural first attempt is one `TrafficLight` class with a `color` field and an `if`/`else` chain (or a `switch`) inside `tick()` that checks the current color to decide how long to stay lit and what to switch to next. Every branch needs to know about every other color just to figure out where to go next, so adding a fifth state - a flashing amber for night mode, say - means touching the same sprawling `tick()` method again. The branches keep growing denser as more colors and edge cases pile in, and it's easy to wire a transition to the wrong color while editing a neighboring branch.
Solution
State takes each branch of that `tick()` chain and turns it into its own class - `RedState`, `YellowState`, `GreenState` - each implementing a common `TrafficLightState` interface with a `handle()` method. The original object, now called the context (`TrafficLight`), stops asking "what color am I" and instead just forwards every `tick()` call to whichever state object it currently holds.
A concrete state's `handle()` does whatever that color should do, and then transitions the context to the next state in the cycle - `RedState` hands off to `YellowState`, `YellowState` to `GreenState`, and `GreenState` back to `RedState`. The context's own code never branches on color at all; it just holds a reference to the current state and delegates, while every state-specific decision and every transition lives inside the state classes themselves.
When to Use
- Reach for State when an object's behavior depends heavily on which of several states it's in, and that set of states keeps growing.
- Use it when a class is dominated by conditionals that all check the same status field to decide how to act.
- Consider it when several near-identical states share so much duplicated transition logic that a shared base state class would remove the repetition.
Real-World Examples
- **Traffic lights** - a signal controller behaves differently in the Red, Yellow, and Green states, each dictating what the next state and timing will be.
- **Order fulfillment workflows** - an e-commerce order moves through Pending, Processing, Shipped, Delivered, and Cancelled, each allowing a different set of actions.
- **TCP sockets** - a connection moves through Listen, Established, and Closed, reacting differently to the same incoming packet depending on which one it's in.
Structure
The Context holds a reference to whichever State object currently represents it and forwards every relevant call to that object instead of branching internally. Each ConcreteState implements the shared State interface with behavior appropriate to one specific state, and may reassign the context's state reference to perform a transition.
Participants
Delegates every state-dependent call to the State object it currently references, and exposes a way for state objects to replace that reference.
In the diagram: Traffic Light.
Declares one method per action the context supports, giving every concrete state a uniform shape the context can call through.
Implements the reaction appropriate to one specific state, and decides whether and to which other state the context should transition next.
In the diagram: Red State, Yellow State, Green State.
class TrafficLightState {
handle(light) { throw new Error('handle() not implemented'); }
toString() { return this.constructor.name; }
}
class RedState extends TrafficLightState {
handle(light) {
console.log('Red: stop.');
light.setState(new YellowState());
}
}
class YellowState extends TrafficLightState {
handle(light) {
console.log('Yellow: get ready.');
light.setState(new GreenState());
}
}
class GreenState extends TrafficLightState {
handle(light) {
console.log('Green: go.');
light.setState(new RedState());
}
}
class TrafficLight {
#state = new RedState();
setState(state) { this.#state = state; }
getState() { return this.#state.toString(); }
tick() { this.#state.handle(this); }
}
const light = new TrafficLight();
light.tick();
light.tick();
light.tick();
light.tick();
console.log(light.getState());
Step 1 of 6
TrafficLightState defines the interface every state implements
The base `handle(light)` throws, and `toString()` returns the concrete class name - `TrafficLight` will only ever call `handle` through this shared shape.
Step 2 of 6
RedState handles its own behavior and picks the next state
`handle` logs `'Red: stop.'` and then calls `light.setState(new YellowState())` - the state itself decides what the next state is, not `TrafficLight`.
Step 3 of 6
YellowState and GreenState follow the identical shape, cycling the light
`YellowState` logs and transitions to `GreenState`; `GreenState` logs and transitions back to `RedState` - together the three states form a closed cycle: Red → Yellow → Green → Red.
Step 4 of 6
TrafficLight delegates entirely to its current state object
`#state` starts as `new RedState()`; `tick()` just calls `this.#state.handle(this)` - the light itself contains no if/else about colors, all that logic lives inside the state classes.
Step 5 of 6
Four ticks cycle through Red, Yellow, Green, and back to Red
Each `light.tick()` calls `handle` on whatever `#state` currently is, which both logs a message and swaps in the next state via `setState` - after four ticks the light has completed one full Red→Yellow→Green→Red cycle.
Step 6 of 6
getState() reports the class name of whatever state is active now
`light.getState()` calls `this.#state.toString()`, which resolves to `this.constructor.name` - after the four ticks above, this prints `'RedState'`.
interface TrafficLightState {
handle(light: TrafficLight): void;
toString(): string;
}
class RedState implements TrafficLightState {
handle(light: TrafficLight): void {
console.log('Red: stop.');
light.setState(new YellowState());
}
toString(): string { return 'RedState'; }
}
class YellowState implements TrafficLightState {
handle(light: TrafficLight): void {
console.log('Yellow: get ready.');
light.setState(new GreenState());
}
toString(): string { return 'YellowState'; }
}
class GreenState implements TrafficLightState {
handle(light: TrafficLight): void {
console.log('Green: go.');
light.setState(new RedState());
}
toString(): string { return 'GreenState'; }
}
class TrafficLight {
private state: TrafficLightState = new RedState();
setState(state: TrafficLightState): void { this.state = state; }
getState(): string { return this.state.toString(); }
tick(): void { this.state.handle(this); }
}
const light = new TrafficLight();
light.tick();
light.tick();
light.tick();
light.tick();
console.log(light.getState());
interface TrafficLightState {
void handle(TrafficLight light);
}
class RedState implements TrafficLightState {
public void handle(TrafficLight light) {
System.out.println("Red: stop.");
light.setState(new YellowState());
}
public String toString() { return "RedState"; }
}
class YellowState implements TrafficLightState {
public void handle(TrafficLight light) {
System.out.println("Yellow: get ready.");
light.setState(new GreenState());
}
public String toString() { return "YellowState"; }
}
class GreenState implements TrafficLightState {
public void handle(TrafficLight light) {
System.out.println("Green: go.");
light.setState(new RedState());
}
public String toString() { return "GreenState"; }
}
class TrafficLight {
private TrafficLightState state = new RedState();
public void setState(TrafficLightState s) { this.state = s; }
public String getState() { return state.toString(); }
public void tick() { state.handle(this); }
}
TrafficLight light = new TrafficLight();
light.tick();
light.tick();
light.tick();
light.tick();
System.out.println(light.getState());
interface ITrafficLightState
{
void Handle(TrafficLight light);
}
class RedState : ITrafficLightState
{
public void Handle(TrafficLight light)
{
Console.WriteLine("Red: stop.");
light.SetState(new YellowState());
}
public override string ToString() => "RedState";
}
class YellowState : ITrafficLightState
{
public void Handle(TrafficLight light)
{
Console.WriteLine("Yellow: get ready.");
light.SetState(new GreenState());
}
public override string ToString() => "YellowState";
}
class GreenState : ITrafficLightState
{
public void Handle(TrafficLight light)
{
Console.WriteLine("Green: go.");
light.SetState(new RedState());
}
public override string ToString() => "GreenState";
}
class TrafficLight
{
private ITrafficLightState _state = new RedState();
public void SetState(ITrafficLightState s) => _state = s;
public string GetState() => _state.ToString()!;
public void Tick() => _state.Handle(this);
}
var light = new TrafficLight();
light.Tick();
light.Tick();
light.Tick();
light.Tick();
Console.WriteLine(light.GetState());
from __future__ import annotations
from abc import ABC, abstractmethod
class TrafficLightState(ABC):
@abstractmethod
def handle(self, light: TrafficLight) -> None: ...
class RedState(TrafficLightState):
def handle(self, light: TrafficLight) -> None:
print('Red: stop.')
light.set_state(YellowState())
class YellowState(TrafficLightState):
def handle(self, light: TrafficLight) -> None:
print('Yellow: get ready.')
light.set_state(GreenState())
class GreenState(TrafficLightState):
def handle(self, light: TrafficLight) -> None:
print('Green: go.')
light.set_state(RedState())
class TrafficLight:
def __init__(self) -> None:
self._state: TrafficLightState = RedState()
def set_state(self, state: TrafficLightState) -> None:
self._state = state
def get_state(self) -> str:
return type(self._state).__name__
def tick(self) -> None:
self._state.handle(self)
light = TrafficLight()
light.tick()
light.tick()
light.tick()
light.tick()
print(light.get_state())
Step 1 of 6
TrafficLightState defines the interface every state implements
`handle(light)` is `@abstractmethod` - `TrafficLight` will only ever call `handle` through this shared interface, never checking the concrete state type.
Step 2 of 6
RedState handles its own behavior and picks the next state
`handle` prints `'Red: stop.'` and then calls `light.set_state(YellowState())` - the state itself decides what the next state is, not `TrafficLight`.
Step 3 of 6
YellowState and GreenState follow the identical shape, cycling the light
`YellowState` prints and transitions to `GreenState`; `GreenState` prints and transitions back to `RedState` - together the three states form a closed cycle: Red → Yellow → Green → Red.
Step 4 of 6
TrafficLight delegates entirely to its current state object
`_state` starts as `RedState()`; `tick()` just calls `self._state.handle(self)` - the light itself contains no if/else about colors, all that logic lives inside the state classes.
Step 5 of 6
Four ticks cycle through Red, Yellow, Green, and back to Red
Each `light.tick()` calls `handle` on whatever `_state` currently is, which both prints a message and swaps in the next state via `set_state` - after four ticks the light has completed one full Red→Yellow→Green→Red cycle.
Step 6 of 6
get_state() reports the class name of whatever state is active now
`light.get_state()` calls `type(self._state).__name__` - after the four ticks above, this prints `'RedState'`.
Click "Run" to execute this code in a sandboxed frame and see console output here.
TypeScript runs as plain JavaScript here - type annotations are stripped, not type-checked.
Click "Run" to execute this code in a sandboxed frame and see console output here.
Advantages
- Each state's rules live in their own class, so understanding what happens in "Paused" means reading one small file instead of tracing a case through a giant method.
- A new state is added by writing a new class, with zero edits to the context or to any state that already works correctly.
- The context's own code shrinks down to plain delegation - there's no conditional logic left in it to accidentally break.
Disadvantages
- For an object with two states and one transition, a full set of state classes is more machinery than the problem calls for.
- Because a state object often decides what the next state should be, following the full transition graph means reading through several state classes rather than one table.
Question 1 of 10
What goes wrong with a single `tick()` method using if/else on the traffic light's color?
Correct! Every branch needs to know about every other color just to figure out where to go next, so adding a fifth state means touching the same sprawling tick() method again, and it's easy to wire a wrong transition.
Not quite. Every branch needs to know about every other color just to figure out where to go next, so adding a fifth state means touching the same sprawling tick() method again, and it's easy to wire a wrong transition.
Question 2 of 10
What does State let an object do at runtime?
Correct! State lets an object switch its entire behavior at runtime by delegating to one of several interchangeable state objects, so from the outside it looks as though the object's class changed.
Not quite. State lets an object switch its entire behavior at runtime by delegating to one of several interchangeable state objects, so from the outside it looks as though the object's class changed.
Question 3 of 10
According to its recommended use cases, when should you reach for State?
Correct! Reach for State when an object's behavior depends heavily on which of several states it's in, and that set of states keeps growing.
Not quite. Reach for State when an object's behavior depends heavily on which of several states it's in, and that set of states keeps growing.
Question 4 of 10
What does the context (TrafficLight) do once State is applied?
Correct! The context stops asking "what color am I" and instead just forwards every tick() call to whichever state object it currently holds.
Not quite. The context stops asking "what color am I" and instead just forwards every tick() call to whichever state object it currently holds.
Question 5 of 10
In the traffic-light example, what does each color need besides its own behavior?
Correct! Each color also has its own duration and its own idea of what the next color should be, which is exactly the kind of per-state logic State is meant to isolate.
Not quite. Each color also has its own duration and its own idea of what the next color should be, which is exactly the kind of per-state logic State is meant to isolate.
Question 6 of 10
Where does the decision about the next state live in the solution?
Correct! A concrete state's handle() does whatever that color should do, and then transitions the context to the next state - every state-specific decision lives inside the state classes.
Not quite. A concrete state's handle() does whatever that color should do, and then transitions the context to the next state - every state-specific decision lives inside the state classes.
Question 7 of 10
According to the pros listed for State, what does understanding the "Paused" state require?
Correct! Each state's rules live in their own class, so understanding what happens in "Paused" means reading one small file instead of tracing a case through a giant method.
Not quite. Each state's rules live in their own class, so understanding what happens in "Paused" means reading one small file instead of tracing a case through a giant method.
Question 8 of 10
When is applying State said to be more machinery than the problem calls for?
Correct! For an object with two states and one transition, a full set of state classes is more machinery than the problem calls for.
Not quite. For an object with two states and one transition, a full set of state classes is more machinery than the problem calls for.
Question 9 of 10
Why can following the full transition graph be harder to trace with State than with a single table?
Correct! Because a state object often decides what the next state should be, following the full transition graph means reading through several state classes rather than one table.
Not quite. Because a state object often decides what the next state should be, following the full transition graph means reading through several state classes rather than one table.
Question 10 of 10
What must every ConcreteState implement to fit into the Context's calls?
Correct! Each ConcreteState implements the shared State interface with behavior appropriate to one specific state, which is what lets the context call through it uniformly.
Not quite. Each ConcreteState implements the shared State interface with behavior appropriate to one specific state, which is what lets the context call through it uniformly.