Modeling an object's behavior changes as swappable state objects rather than internal flags — via a traffic light, and when the extra class structure actually earns its keep over a plain enum + switch.
Published September 23, 2026
When an object's methods behave differently depending on some internal mode/status, State models each mode as its own class implementing a shared interface — the object delegates to its "current state" object instead of branching internally on a status field.
interface TrafficLightState {
void next(TrafficLight light); // transition logic lives IN the state
String displayColor();
}
class RedState implements TrafficLightState {
public void next(TrafficLight light) { light.setState(new GreenState()); }
public String displayColor() { return "RED"; }
}
class GreenState implements TrafficLightState {
public void next(TrafficLight light) { light.setState(new YellowState()); }
public String displayColor() { return "GREEN"; }
}
class YellowState implements TrafficLightState {
public void next(TrafficLight light) { light.setState(new RedState()); }
public String displayColor() { return "YELLOW"; }
}
class TrafficLight {
private TrafficLightState state = new RedState();
void setState(TrafficLightState state) { this.state = state; }
void next() { state.next(this); } // delegates entirely to the current state object
String getColor() { return state.displayColor(); }
}
TrafficLight light = new TrafficLight();
light.getColor(); // "RED"
light.next();
light.getColor(); // "GREEN"
TrafficLight itself contains zero conditional logic about colors or transitions — every state object knows its own display value and exactly which state comes next, and TrafficLight.next() simply asks the current state object to handle the transition.
// The alternative — often perfectly fine for simple cases
enum LightColor { RED, GREEN, YELLOW }
class TrafficLight {
private LightColor color = LightColor.RED;
void next() {
color = switch (color) {
case RED -> LightColor.GREEN;
case GREEN -> LightColor.YELLOW;
case YELLOW -> LightColor.RED;
};
}
}
For three states with one trivial transition rule each, the enum+switch version is genuinely simpler — fewer classes, easier to read at a glance. State pattern earns its extra structure when: states have meaningfully different behavior beyond a label (not just a different next-state, but different validation rules, different allowed operations, different data), when the number of states or transition rules is large enough that a single switch statement becomes unwieldy, or when individual states need their own state (e.g. an OrderState.Shipped holding a tracking number that OrderState.Pending doesn't have anywhere to put). An OrderState machine (Pending → Paid → Shipped → Delivered, each with different allowed actions and different associated data) is a much stronger case for full State-pattern classes than a three-color traffic light.
Q: Are State instances typically stateless themselves — can they be shared/cached as singletons?
A: Often yes, when a state object holds no instance data of its own (like RedState above) — a single shared RedState instance can be reused across every TrafficLight, avoiding repeated allocation. States that carry their own data (like the OrderState.Shipped example) can't be shared this way, since each context needs its own instance with its own data.
Q: How is State different from Strategy, since both delegate to a swappable object implementing a shared interface?
A: Structurally nearly identical — the real difference is who controls the swap and why. In Strategy, the client explicitly chooses and sets the strategy, usually once, for a specific purpose (which algorithm to use). In State, the state objects themselves typically decide and trigger the next transition (as next() does above) — state transitions are usually driven by the object's own internal logic, not by an external caller deciding to swap behavior for unrelated reasons.
Q: What's a failure mode of the plain enum + switch version as a system grows? A: Every new piece of state-dependent behavior (not just color, but say permitted actions, next valid states, associated validation) means adding another switch statement scattered somewhere else in the codebase, keyed on the same enum — these switches tend to drift out of sync with each other over time, a maintenance smell State pattern avoids by keeping all of one state's behavior colocated in one class.
Q: Could invalid transitions be prevented more strongly than by convention? A: Yes — instead of every state's next() being free to transition to any other state class, each state's next() method can be written to only know about its own legal successor(s), which is exactly what the RedState -> GreenState -> YellowState -> RedState chain above already does implicitly: RedState has no way to transition anywhere except GreenState, because that's the only class it references.