skip to content

The Strategy and State patterns have nearly identical class diagrams — a context delegating to an interface with several implementations. How do they actually differ, and what does that difference change in the code?

level: middleimportance: must knowfreq 58%

answer

  1. identical diagram, different intent
  2. Strategy: client picks an algorithm
  3. State: events drive the mode
  4. states know successors; strategies don't
  5. State methods may legally reject

basics

~20 s

Same structure, different intent. In Strategy the client picks one algorithm and it usually stays fixed; the variants don't know about each other. In State the object's own behavior changes as its lifecycle advances, and states typically trigger the transitions to the next state.

solid answer

~50 s

Structurally both are: a Context holding a reference to an interface implemented by several classes, delegating to it. The differences are about intent and dynamics. **Strategy**: the variants are *alternative algorithms for the same task* (compression codecs, pricing rules, sort orders). The client chooses one — usually once at construction — because it knows what it wants. Strategies are mutually oblivious; none decides to replace itself. The Context's *identity* doesn't change, only how it computes. **State**: the variants are *modes of a lifecycle* (Draft → Submitted → Approved). Nobody outside chooses the state directly; it results from events. The current state usually knows its legal successors and performs the transition (`context.setState(next)`), so the implementations are coupled to each other by design, and the state object often exposes several methods (one per event) that behave differently or reject the event. Practical test: if variants swap themselves as a result of what happened, it's State. If an outsider selects among equals, it's Strategy.

code

typescript · 16 lines
typescript
// STATE: the variant performs its own transition and rejects illegal events
interface OrderState { pay(o: Order): void; ship(o: Order): void }

class New implements OrderState {
  pay(o)  { o.state = new Paid(); }
  ship(o) { throw new Error("cannot ship before payment"); }
}
class Paid implements OrderState {
  pay(o)  { throw new Error("already paid"); }
  ship(o) { o.state = new Shipped(); }
}

// STRATEGY: an outsider picks; no variant ever replaces another
interface Tax { of(o: Order): number }
const vat: Tax = { of: o => o.subtotal * 0.2 };
const zero: Tax = { of: () => 0 };

go deeper

for a junior

Say the diagrams match but the intent differs: Strategy = pick an algorithm; State = behavior changes as the object moves through its lifecycle. Give one example of each.

for a middle

Add the mechanics: who chooses, how often it changes, that states usually perform transitions and may reject illegal events, and that strategy interfaces are usually one cohesive operation.

for a senior

Discuss where transition logic belongs (in states vs. a central table), Liskov implications of methods that reject, and how the choice changes the tests you write.

for a principal

Talk about explicit state machines as a modeling discipline — auditability, persisted state, illegal-transition telemetry, workflow engines — and the orthogonality of lifecycle state versus pluggable policy in the same aggregate.

## Why the confusion exists Both patterns are drawn identically in UML: `Context ──▶ IThing`, with `ThingA`, `ThingB`, `ThingC` implementing `IThing`, and `Context` delegating. Patterns are **not** identified by their class diagram — they are identified by the *problem they solve* and by the *forces* around them. Strategy and State are the classic proof of that. ## Definitions - **Strategy**: "Define a family of algorithms, encapsulate each one, and make them interchangeable." The varying thing is *how* a task is done. - **State**: "Allow an object to alter its behavior when its internal state changes; the object will appear to change its class." The varying thing is *what the object currently is*, i.e. which operations are legal and what they do right now. ## The five practical differences **1. Who chooses.** Strategy: the *client* — a factory, config value, request field, DI wiring. It knows which algorithm it wants and says so. State: *nobody chooses directly*; the state is a consequence of the history of events. A caller sends `submit()`, and the object ends up in `Submitted` because the rules said so. **2. How often it changes.** Strategy is usually set once and left alone (though runtime swapping is allowed and normal). State changes repeatedly over the object's lifetime — that's the whole point. **3. Coupling between variants.** Strategy implementations are *mutually ignorant*: `GzipCodec` has never heard of `BrotliCodec`. State implementations typically know their successors: `DraftState.submit()` sets the context's state to `SubmittedState`. That inter-variant knowledge — the transition table spread across the classes — is a State signature. (Alternative: centralize transitions in the Context or in a table, which decouples the states but moves the machine's rules into one place.) **4. Shape of the interface.** Strategy is usually one cohesive operation (`compress(bytes)`, `price(order)`). State is usually several operations, one per event (`submit()`, `approve()`, `reject()`, `cancel()`), and a given state legitimately answers some by throwing / returning a rejection, because that transition is illegal in that mode. "This implementation deliberately refuses half its methods" is fine in State (each state is a partial function of the lifecycle) and is a smell in Strategy (it breaks Liskov substitutability — the promise that any implementation can stand in for the interface). **5. What the client observes.** Under Strategy, callers see the same behavior contract before and after; only the internal computation differs. Under State, callers see the *object itself* behave differently over time — the same call succeeds now and fails later. ## Worked contrast Strategy — pricing: ``` interface Pricing { price(order): Money } checkout = Checkout(pricing = BlackFridayPricing()) // client decides ``` Nothing in `BlackFridayPricing` will ever say "now become `RegularPricing`". State — an order lifecycle: ``` interface OrderState { pay(ctx); ship(ctx); cancel(ctx) } class New implements OrderState { pay(ctx) { ctx.state = Paid() } // the state performs the transition ship(ctx) { throw IllegalTransition() } // illegal right now cancel(ctx) { ctx.state = Cancelled() } } class Paid implements OrderState { pay(ctx) { throw AlreadyPaid() } ship(ctx) { ctx.state = Shipped() } cancel(ctx) { ctx.state = Refunding() } } ``` Here `New` names `Paid`; that dependency is intended. ## Edge cases and grey zones - **A strategy that swaps itself.** An adaptive algorithm that starts with a simple heuristic and upgrades to an expensive one after N calls looks like State creeping into Strategy. If the switching rule is genuinely part of the task, keep it in the Context (or in a wrapping strategy that owns the delegation) rather than letting one algorithm hand-pick its replacement. - **A state machine with one method.** A retry policy that goes closed → open → half-open (the Circuit Breaker) is a state machine even though callers only ever call `execute()`; the modes are lifecycle, not client-chosen alternatives. - **Both at once.** An object can be in a State *and* hold a Strategy: an `Order` in `Paid` state that also uses a pluggable `TaxCalculator`. They are orthogonal axes. - **Neither.** If "states" carry no behavior at all, an enum field plus validation is simpler than a class per state. State pays off when each mode has real behavior and the transition table is non-trivial. ## Why the distinction matters in practice Misreading a state machine as Strategy produces a Context stuffed with `if (currentState == ...)` guards before every delegation — the transition rules leak out and duplicate. Misreading Strategy as State produces algorithms that reach into the context to reassign themselves, creating cycles between classes that should never have known each other. Naming the intent up front also names the tests you write: for Strategy you test each algorithm's outputs; for State you test the transition table — legal transitions, illegal ones rejected, and terminal states.

  • Should the transition logic live inside the state classes or in the context?
    Both are valid. Inside the states keeps each mode's rules local and adds no central switch, but couples states to their successors. In the context (or a transition table) decouples the states and makes the whole machine readable in one place — better when the graph is large, audited, or configuration-driven.
  • If a state class throws on half of its methods, isn't that a Liskov Substitution Principle violation?
    Strictly it weakens substitutability, and that is exactly why State is the right label: the interface models lifecycle events, not a uniform algorithm, and rejecting an illegal event is meaningful behavior. In Strategy the same shape would be a design smell.
  • Can a single class be both a Strategy and a State implementation?
    It can be reused, but the intents should stay separate. Mixing them usually means one interface is doing two jobs; split the pluggable algorithm from the lifecycle mode so each can change independently.

Strategy is choosing your route app's mode — fastest, shortest, avoid tolls; you pick, and it stays. State is a traffic light: nobody picks 'red', it becomes red because green's timer expired, and green is what knows amber comes next.

saying these in an interview costs you the question

  • "They're the same pattern" — same structure, different intent, different coupling, different tests.
  • Claiming states must never know each other; state-driven transitions are a standard, intended State variant.
  • Claiming a Strategy can never be replaced at runtime — it can; the distinction is *who* decides and *why*.
  • Modeling a lifecycle with an enum plus `switch` in ten places and calling it Strategy.
  • Letting a Strategy assign the Context a different Strategy, creating a hidden state machine nobody documented.

context