Explain how the State, Strategy, and Command patterns differ, even though all three delegate behaviour to a separate object.
answer
- Strategy: client picks how; siblings ignore each other
- State: object picks next; states know each other = FSM
- Command: request as data → queue, log, replay, undo
- State/Strategy share the diagram, differ in who transitions
- Command + Memento = undo stack
basics
~20 sStrategy plugs in one interchangeable algorithm chosen by the caller. State makes an object behave differently as its internal state changes, with states deciding the transitions. Command wraps a request — action plus its arguments — into an object you can queue, log, or undo.
solid answer
~60 sAll three replace conditionals with a delegate object, but the delegate means something different. In Strategy the client chooses an algorithm and injects it; the strategies are independent of each other and typically fixed for the operation. In State the object holds a current state object that implements the same interface; each state knows which state comes next, so the set of state classes together encodes a finite state machine and the delegate changes over the object's lifetime in response to events. Structurally State and Strategy are the same diagram — the difference is who chooses and whether the delegates know about each other. Command is different in kind: it does not vary an algorithm, it reifies a *request* — receiver, action and arguments bundled into an object with an `execute()` method. That reification is what buys queuing, retry, scheduling, logging, macro composition and undo (via an `undo()` or a captured Memento). Rule of thumb: varying how → Strategy; varying by lifecycle stage with transitions → State; needing to store, defer, or reverse an invocation → Command.
code
pseudocode · 15 lines// Strategy: client picks the algorithm
checkout = Checkout(strategy = BlackFridayPricing())
// State: the state object picks the successor
class Draft : OrderState {
submit(order) { order.state = Submitted() } // states know transitions
ship(order) { throw IllegalTransition() } // illegal by construction
}
// Command: the invocation becomes data
class MoveShape(shape, dx, dy) : Command {
execute() { shape.moveBy(dx, dy) }
undo() { shape.moveBy(-dx, -dy) }
}
history.push(cmd); cmd.execute() // queue it, log it, reverse itgo deeper
Give the one-line intent of each and one example: pluggable algorithm, lifecycle-driven behaviour, request-as-object.
Explain that State and Strategy share a diagram and distinguish them by who owns transitions; name Command's queue/log/undo payoffs.
Discuss illegal-transition safety versus class proliferation, persistence of state, undo via Memento versus inverse commands, and when reification is not paid for.
Connect to system design: commands as messages (CQRS, event sourcing, write-ahead logs), state machines as the backbone of workflow/saga engines and circuit breakers, and the operational cost of behaviour scattered across many small types.
## Why they get confused Drawn as class diagrams, **Strategy** and **State** are literally identical: a context holds a reference to an interface, concrete classes implement it, the context delegates. **Command** also has a context calling an interface. The differences are in **intent, lifetime, and who controls the delegate** — which is exactly what interviewers probe. ## Strategy — interchangeable algorithm **Intent.** Define a family of algorithms, encapsulate each, make them interchangeable so the algorithm varies independently of the clients that use it. **Who chooses.** The client (or a factory / DI container / config). The context receives a strategy and uses it. **Lifetime.** Usually stable for the call or the object's life. Swapping is possible but not the driver. **Do strategies know each other?** No — they are independent siblings. `QuickSort` has no idea `MergeSort` exists. **Typical uses.** Sorting comparators, compression codecs, pricing/discount rules, routing algorithms, retry backoff policies, serialization formats. **Signal to apply it.** A `switch`/`if` over a *kind* value that selects how to compute something, repeated in more than one place. ## State — behaviour that changes with lifecycle **Intent.** Allow an object to alter its behaviour when its internal state changes; the object appears to change class. **Who chooses.** The object itself, or more commonly the current state object decides the transition when an event arrives (`this.context.setState(new Shipped())`). The client just sends events (`order.pay()`, `order.ship()`). **Lifetime.** The delegate changes repeatedly over the object's life — that churn *is* the pattern. **Do states know each other?** Usually yes, because a state names its successors. That mutual knowledge is the sharpest distinguishing test from Strategy. (An alternative keeps the transition table in the context, trading state autonomy for a single readable map of the machine.) **Typical uses.** Order/document lifecycles (draft → submitted → approved → shipped), TCP connection states, media player (stopped/playing/paused), circuit breaker (closed/open/half-open), workflow engines. **Benefits over a status enum + switch.** Illegal transitions become impossible-by-construction rather than runtime `if` checks; each state's rules live in one cohesive class; adding a state does not touch every switch. **Costs.** Many small classes; the machine as a whole is spread across files (mitigate with a diagram or a transition table); persisting state means mapping a class back to a stored discriminator. ## Command — a request as an object **Intent.** Encapsulate a request as an object, so you can parameterize clients with different requests, queue or log them, and support undo. **Roles.** *Command* (interface with `execute()`), *ConcreteCommand* (holds the receiver plus the arguments), *Receiver* (does the real work), *Invoker* (holds and triggers commands, e.g. a button, scheduler, or queue), *Client* (creates and configures commands). **What reification buys.** Because the invocation is now data, you can: - **Queue** it (job/task queues, thread pools — a `Runnable` is a Command). - **Schedule / defer** it. - **Log** it, and replay the log to rebuild state (this is the essence of write-ahead logs and event sourcing). - **Compose** it (a macro command containing a list of commands — often a Composite). - **Undo/redo** it, either by implementing an inverse `undo()` or by capturing a **Memento** of the receiver before execution. The undo stack is the canonical Command use case. - **Transport** it across a boundary (a serialized command is effectively a message/DTO — the basis of CQRS command handlers). **Costs.** One class per action; parameter capture must be immutable or you get action-at-a-distance; undo correctness is hard when the receiver changed in between (concurrent edits, external side effects that cannot be reversed — an email is sent, a payment is captured; those need compensating actions rather than undo). ## Side-by-side | | Strategy | State | Command | |---|---|---|---| | Encapsulates | an algorithm | behaviour for one lifecycle stage | an invocation (receiver + action + args) | | Chosen by | the client | the object / current state | the client, executed by an invoker | | Changes over time | rarely | constantly, that's the point | each request is a new instance | | Delegates know each other | no | typically yes (transitions) | no | | Killer feature | swap how it's computed | illegal transitions impossible | queue, log, replay, undo | ## Edge cases and combinations - **Function-typed languages** shrink all three: Strategy is a lambda, Command is a closure (a zero-arg function *is* an `execute()`), and even State can be a function returning the next state. The named pattern still helps when you need extra members — a Command's `undo()` and `describe()`, a state's `canTransitionTo()`. - **State + Command** combine naturally: commands drive transitions and the command log becomes the audit trail of the machine. - **Command + Memento** is the standard undo implementation when computing an inverse is impractical. - Mistaking a **stateless Strategy for State** produces a context that must externally decide the next algorithm — the transition logic then leaks into the client, which is the exact smell State removes. - A **Command whose `execute()` just calls one method with no queuing, logging, or undo** is usually over-engineering; the pattern earns its keep only when you exploit the reification.
- You have a class diagram that could be either State or Strategy. What single question settles it?Ask who decides the next delegate. If the client injects it and the implementations never reference one another, it is Strategy. If the object transitions itself and the implementations name their successors — encoding a state machine — it is State.
- How do you implement undo when the action cannot be inverted arithmetically?Capture a Memento — a snapshot of the receiver's relevant internal state taken before `execute()` — and restore it on `undo()`. It costs memory proportional to snapshot size, so real editors mix strategies: cheap inverse commands where possible, periodic snapshots plus command replay otherwise. Genuinely irreversible external effects (sent emails, captured payments) need compensating actions, not undo.
- When is a Command object over-engineering?When nothing consumes the reification. If every command is created and executed immediately, with no queue, no log, no undo, no scheduling and no cross-boundary transport, you have added a class and an indirection to replace a direct method call.
Strategy is choosing which route-planning app to use — you pick it, it plans, it never thinks about the other apps. State is a traffic light — it changes its own behaviour on a fixed cycle and each colour knows which comes next. Command is a written work order — the instruction and its details on paper, so it can be filed, queued, handed to someone else, or voided later.
saying these in an interview costs you the question
- Saying State and Strategy are the same pattern because the diagrams match, without naming the transition-ownership difference.
- Claiming Command is about varying an algorithm — it reifies an invocation.
- Assuming every Command must support undo; undo is one of several benefits of reification.
- Describing State as just a status field with a switch — the pattern's value is making illegal transitions unrepresentable.
- Introducing Command with no queue, log, or undo, then calling it decoupling.