Behavioral Complexity: Medium

State Pattern

Allow an object to alter its behavior when its internal state changes.

The Problem

An order goes through multiple states (pending, confirmed, shipped, delivered, cancelled) with conditional logic in every method. Each operation must check the current state and behave differently: confirming a pending order succeeds, but confirming a shipped order fails. Scattering these conditionals throughout the code makes it fragile and hard to extend.

The Solution

The State pattern delegates state-specific behavior to individual state objects. Each state knows which transitions are valid and throws errors for invalid ones. The context (OrderContext) holds the current state and delegates operations to it.

Structure

  • State (interface OrderState) — Declares confirm(), ship(), deliver(), cancel().
  • Context (OrderContext) — Holds the current state and an audit trail of transitions.
  • Concrete StatesPendingState, ConfirmedState, ShippedState, DeliveredState, CancelledState.
  • Service (StateService) — Manages order contexts.

Implementation

Valid transitions:

Pending  --confirm-->  Confirmed  --ship-->  Shipped  --deliver-->  Delivered
Pending  --cancel-->   Cancelled
Confirmed --cancel-->  Cancelled
export interface StateTransitionResult {
  success: boolean;
  message: string;
  newState: string;
}

export interface OrderState {
  getName(): string;
  confirm(context: OrderStateContext): StateTransitionResult;
  ship(context: OrderStateContext): StateTransitionResult;
  deliver(context: OrderStateContext): StateTransitionResult;
  cancel(context: OrderStateContext): StateTransitionResult;
}

export interface OrderStateContext {
  setState(state: OrderState): void;
  getStateName(): string;
}

NestJS Integration

The StateService is an @Injectable() singleton that manages a Map of order contexts. Individual state objects are plain classes created on demand — they are lightweight and stateless, so they don’t need to be NestJS providers. The context and state objects form a self-contained state machine that the service exposes via simple methods.

When to Use

  • An object’s behavior depends on its current state and must change at runtime.
  • Operations have massive conditional logic based on the object’s state.
  • You want to make state transitions explicit and enforce valid transition rules.

When NOT to Use

  • The object has only two or three states with simple transitions — a boolean or enum with a switch statement is clearer.
  • State transitions are rare and the overhead of state objects is not justified.