State Pattern
Allows an object to alter its behavior when its internal state changes. The object appears to change its class. State encapsulates state-specific behavior into separate state objects, eliminating complex conditional logic and making state transitions explicit.
✦ Problem
An object behaves differently depending on its current state, and state transitions are triggered by various events. Using large conditional statements (if/else or switch) to manage states leads to bloated, fragile code. Every time you add a new state, you must modify every conditional in the class. The class becomes impossible to test in isolation.
✦ Solution
Create state classes for each possible state, implementing a common interface. The context object stores a reference to the current state and delegates state-specific work to it. State transitions are performed by replacing the current state object with another. Each state knows which states it can transition to.
Participants
| Role | Responsibility |
|---|---|
| Context | Maintains a reference to the current State and delegates state-specific behavior to it |
| State | Defines an interface for encapsulating the behavior associated with a particular state |
| ConcreteState | Each subclass implements behavior associated with a state of the Context |
TypeScript Example
interface ConnectionState {
connect(ctx: TcpConnection): void;
send(ctx: TcpConnection, data: string): void;
disconnect(ctx: TcpConnection): void;
name: string;
}
class TcpConnection {
state: ConnectionState = new ClosedState();
connect() { this.state.connect(this); }
send(data: string) { this.state.send(this, data); }
disconnect() { this.state.disconnect(this); }
setState(state: ConnectionState) {
console.log(` [Transition] ${this.state.name} → ${state.name}`);
this.state = state;
}
}
class ClosedState implements ConnectionState {
name = "CLOSED";
connect(ctx: TcpConnection) {
console.log("Opening connection...");
ctx.setState(new OpenState());
}
send(_ctx: TcpConnection, _data: string) {
console.log("Error: cannot send, connection closed");
}
disconnect(_ctx: TcpConnection) {
console.log("Already closed");
}
}
class OpenState implements ConnectionState {
name = "OPEN";
connect(_ctx: TcpConnection) { console.log("Already connected"); }
send(_ctx: TcpConnection, data: string) {
console.log(`Sending: ${data}`);
}
disconnect(ctx: TcpConnection) {
console.log("Closing connection...");
ctx.setState(new ClosedState());
}
}
const conn = new TcpConnection();
conn.send("hello"); // Error: cannot send
conn.connect(); // Opening -> OPEN
conn.send("hello"); // Sending: hello
conn.disconnect(); // Closing -> CLOSEDReal-World Example
Vending machines transition between states (idle, coin inserted, dispensing, out of stock) and each state handles button presses differently. TCP connections cycle through states (LISTEN, SYN_SENT, ESTABLISHED, CLOSED). Media players change behavior based on state (playing, paused, stopped). Order management systems process orders through states (pending, confirmed, shipped, delivered, refunded).
✓ When to Use
- •An object's behavior depends on its state, and it must change behavior at runtime depending on that state
- •Operations have large conditional statements that depend on the object's state
- •You want to make state transitions explicit and self-documenting
- •You want to ensure that all states implement the same interface consistently
✗ When Not to Use
- •The object has very few states or state transitions are trivial
- •The state-specific behavior is minimal, and conditionals are easier to understand
- •State objects need to share significant amounts of context data, leading to tight coupling
Understanding the State Pattern
The State pattern is one of the most important behavioral design patterns in object-oriented software design. Originally cataloged by the Gang of Four (Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides) in their seminal 1994 book "Design Patterns: Elements of Reusable Object-Oriented Software," this pattern continues to be a cornerstone of modern software architecture.
Allows an object to alter its behavior when its internal state changes. The object appears to change its class. State encapsulates state-specific behavior into separate state objects, eliminating complex conditional logic and making state transitions explicit. The pattern involves the following key participants: Context, State, ConcreteState. Each participant has a well-defined role, and the interactions between them create a flexible, maintainable structure that can evolve with changing requirements.
In TypeScript and modern JavaScript development, the State pattern is particularly valuable because the language's type system and interface support allow you to express the pattern with compile-time safety. TypeScript generics can make State implementations even more flexible and reusable while maintaining strong type checking throughout the pattern's structure.
Knowing when to apply the State pattern is as important as knowing how. Common use cases include: An object's behavior depends on its state, and it must change behavior at runtime depending on that state; Operations have large conditional statements that depend on the object's state; You want to make state transitions explicit and self-documenting. However, overusing design patterns can lead to unnecessarily complex code. Always evaluate whether the pattern genuinely solves your problem or if a simpler approach would suffice.
The State pattern is related to several other design patterns: Strategy, Bridge, Singleton, Flyweight. Understanding these relationships helps you choose the right pattern for your specific situation and combine patterns effectively when building complex systems.
Related Patterns
Defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary …
Decouples an abstraction from its implementation so that the two can vary independently. The Bridge pattern achieves thi…
Ensures a class has only one instance and provides a global point of access to it. The Singleton pattern restricts insta…
Uses sharing to support large numbers of fine-grained objects efficiently. Flyweight reduces memory usage by sharing as …
More Behavioral Patterns
Explore All Design Patterns
Browse our complete reference of 23 design patterns with TypeScript examples, UML participants, and practical guidance.