BehavioralTypeScriptverifiedVerified
State Pattern in TypeScript
Allows an object to alter its behavior when its internal state changes, appearing as if the object has changed its class.
How to Implement the State Pattern in TypeScript
1Step 1: Define the state interface and color type
type TrafficLightColor = "red" | "green" | "yellow";
interface TrafficLightState {
readonly color: TrafficLightColor;
next(): TrafficLightState;
canGo(): boolean;
}2Step 2: Implement concrete state classes
class RedLight implements TrafficLightState {
readonly color = "red" as const;
next(): TrafficLightState { return new GreenLight(); }
canGo(): boolean { return false; }
}
class GreenLight implements TrafficLightState {
readonly color = "green" as const;
next(): TrafficLightState { return new YellowLight(); }
canGo(): boolean { return true; }
}
class YellowLight implements TrafficLightState {
readonly color = "yellow" as const;
next(): TrafficLightState { return new RedLight(); }
canGo(): boolean { return false; }
}3Step 3: Create the context that delegates to states
class TrafficLight {
private state: TrafficLightState = new RedLight();
advance(): void {
this.state = this.state.next();
}
getColor(): TrafficLightColor {
return this.state.color;
}
canGo(): boolean {
return this.state.canGo();
}
}4Step 4: Cycle through states and observe transitions
const light = new TrafficLight();
console.log(light.getColor(), light.canGo()); // red false
light.advance();
console.log(light.getColor(), light.canGo()); // green true
light.advance();
console.log(light.getColor(), light.canGo()); // yellow false
light.advance();
console.log(light.getColor(), light.canGo()); // red false// ── Order Lifecycle State Machine ─────────────────────────────────
type OrderStatus =
| "pending"
| "confirmed"
| "processing"
| "shipped"
| "delivered"
| "cancelled"
| "refunded";
interface OrderContext {
orderId: string;
items: Array<{ sku: string; quantity: number; price: number }>;
total: number;
statusHistory: Array<{ status: OrderStatus; at: number; note?: string }>;
}
interface OrderState {
readonly status: OrderStatus;
confirm(order: Order): void;
process(order: Order): void;
ship(order: Order, trackingCode: string): void;
deliver(order: Order): void;
cancel(order: Order, reason: string): void;
refund(order: Order): void;
}
class InvalidTransitionError extends Error {
constructor(from: OrderStatus, to: string) {
super(`Cannot transition from "${from}" to "${to}"`);
}
}
abstract class BaseOrderState implements OrderState {
abstract readonly status: OrderStatus;
confirm(_order: Order): void { throw new InvalidTransitionError(this.status, "confirmed"); }
process(_order: Order): void { throw new InvalidTransitionError(this.status, "processing"); }
ship(_order: Order, _code: string): void { throw new InvalidTransitionError(this.status, "shipped"); }
deliver(_order: Order): void { throw new InvalidTransitionError(this.status, "delivered"); }
cancel(_order: Order, _reason: string): void { throw new InvalidTransitionError(this.status, "cancelled"); }
refund(_order: Order): void { throw new InvalidTransitionError(this.status, "refunded"); }
}
class PendingState extends BaseOrderState {
readonly status = "pending" as const;
confirm(order: Order): void { order.transitionTo(new ConfirmedState()); }
cancel(order: Order, reason: string): void { order.transitionTo(new CancelledState(reason)); }
}
class ConfirmedState extends BaseOrderState {
readonly status = "confirmed" as const;
process(order: Order): void { order.transitionTo(new ProcessingState()); }
cancel(order: Order, reason: string): void { order.transitionTo(new CancelledState(reason)); }
}
class ProcessingState extends BaseOrderState {
readonly status = "processing" as const;
ship(order: Order, trackingCode: string): void { order.transitionTo(new ShippedState(trackingCode)); }
}
class ShippedState extends BaseOrderState {
readonly status = "shipped" as const;
constructor(public readonly trackingCode: string) { super(); }
deliver(order: Order): void { order.transitionTo(new DeliveredState()); }
}
class DeliveredState extends BaseOrderState {
readonly status = "delivered" as const;
refund(order: Order): void { order.transitionTo(new RefundedState()); }
}
class CancelledState extends BaseOrderState {
readonly status = "cancelled" as const;
constructor(public readonly reason: string) { super(); }
}
class RefundedState extends BaseOrderState {
readonly status = "refunded" as const;
}
// ── Order (Context) ───────────────────────────────────────────────
class Order {
private state: OrderState = new PendingState();
readonly context: OrderContext;
constructor(orderId: string, items: OrderContext["items"]) {
this.context = {
orderId,
items,
total: items.reduce((sum, i) => sum + i.price * i.quantity, 0),
statusHistory: [{ status: "pending", at: Date.now() }],
};
}
transitionTo(state: OrderState, note?: string): void {
this.state = state;
this.context.statusHistory.push({ status: state.status, at: Date.now(), note });
}
get status(): OrderStatus { return this.state.status; }
confirm(): void { this.state.confirm(this); }
process(): void { this.state.process(this); }
ship(trackingCode: string): void { this.state.ship(this, trackingCode); }
deliver(): void { this.state.deliver(this); }
cancel(reason: string): void { this.state.cancel(this, reason); }
refund(): void { this.state.refund(this); }
}
export { Order, type OrderStatus, type OrderContext, InvalidTransitionError };State Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
State Pattern in the Real World
“Think of a traffic light. The light itself (context) doesn't change its wiring, but its active state—red, yellow, or green—completely determines what drivers should do. Each color has its own rules, and the light transitions through states on a timer. Adding a flashing-yellow state only requires defining that state's rules, not rewiring the entire light.”