Command Pattern
The Command pattern works by turning a method call into a first-class object: instead of the invoker calling receiver.turnOn() directly, it holds a Command object and calls command.execute() — which means the request can now be stored, queued, logged, replayed, and (if the command also remembers how to reverse itself via undo()) put on a history stack and rewound. That last capability — a command that captures enough state to invert its own effect — is the whole reason undo/redo, macros, and transactional batches fall out of this pattern, and it is exactly the mechanism a light-switch demo usually omits.
The four roles, concretely
- Command — an interface with
execute()and, crucially for undo,undo(). A command is a closure-in-an-object: it bundles what to do, on whom, and with what arguments. - Receiver — the object that knows how to actually do the work (the
Light, the document buffer, the bank account). The command never reimplements the work; it delegates. - Invoker — holds and triggers commands but knows nothing about the receiver. The remote, the menu item, the job-queue worker. Because it only sees the
Commandinterface, it can run, record, and reverse any command uniformly. - Client — wires a concrete command to its receiver and hands it to the invoker.
The non-obvious member is the invoker when it grows a history stack. The moment the invoker pushes every executed command onto a stack, "undo" is just "pop and call undo()" and "redo" is "pop from a second stack and call execute()" — no receiver needs to know undo exists.
Worked trace: a dimmable light with undo/redo
The receiver is a Light with a numeric brightness (0 = off, 100 = full). Each command captures the brightness before it runs so it can restore it. Watch the two stacks as the user clicks.
| Step | Action | Command run | brightness | undo stack (top→) | redo stack |
|---|---|---|---|---|---|
| 0 | start | — | 0 | [] | [] |
| 1 | press On | OnCmd: prev=0, set 40 | 40 | [OnCmd] | [] |
| 2 | press Dim 70 | DimCmd: prev=40, set 70 | 70 | [DimCmd, OnCmd] | [] |
| 3 | undo | pop DimCmd → undo(): set prev=40 | 40 | [OnCmd] | [DimCmd] |
| 4 | undo | pop OnCmd → undo(): set prev=0 | 0 | [] | [DimCmd, OnCmd] |
| 5 | redo | pop OnCmd → execute(): set 40 | 40 | [OnCmd] | [DimCmd] |
| 6 | press On again | OnCmd2: prev=40, set 40; redo cleared | 40 | [OnCmd2, OnCmd] | [] |
Step 6 shows the one rule people forget: any fresh action clears the redo stack. Once you branch off the timeline, the future you undid is gone — exactly how every real editor behaves.
Implementation (Java)
Note undo() on the interface and the prevBrightness field each command captures in its constructor — that field is the entire trick. The invoker owns the two stacks.
import java.util.*;
interface Command {
void execute();
void undo();
}
// Receiver
class Light {
private int brightness = 0; // 0..100
int getBrightness() { return brightness; }
void setBrightness(int b) {
brightness = Math.max(0, Math.min(100, b));
System.out.println("Light brightness = " + brightness);
}
}
// Concrete command: turn on to a target level
class OnCommand implements Command {
private final Light light;
private final int target;
private int prevBrightness; // captured at execute time
OnCommand(Light light, int target) { this.light = light; this.target = target; }
public void execute() {
prevBrightness = light.getBrightness(); // remember how to invert
light.setBrightness(target);
}
public void undo() { light.setBrightness(prevBrightness); }
}
// Concrete command: dim to a level (same shape, different intent)
class DimCommand implements Command {
private final Light light;
private final int level;
private int prevBrightness;
DimCommand(Light light, int level) { this.light = light; this.level = level; }
public void execute() {
prevBrightness = light.getBrightness();
light.setBrightness(level);
}
public void undo() { light.setBrightness(prevBrightness); }
}
// Invoker with history
class RemoteControl {
private final Deque<Command> undoStack = new ArrayDeque<>();
private final Deque<Command> redoStack = new ArrayDeque<>();
void press(Command c) {
c.execute();
undoStack.push(c);
redoStack.clear(); // a new action invalidates the redo future
}
void undo() {
if (undoStack.isEmpty()) return;
Command c = undoStack.pop();
c.undo();
redoStack.push(c);
}
void redo() {
if (redoStack.isEmpty()) return;
Command c = redoStack.pop();
c.execute();
undoStack.push(c);
}
}
public class Solution {
public static void main(String[] args) {
Light light = new Light();
RemoteControl remote = new RemoteControl();
remote.press(new OnCommand(light, 40)); // 40
remote.press(new DimCommand(light, 70)); // 70
remote.undo(); // 40
remote.undo(); // 0
remote.redo(); // 40
}
}Why the naive version is wrong
A common buggy undo() hard-codes the inverse: OnCommand.undo() just calls light.turnOff(). That assumes the light was off before — but in the trace at step 6 it was already at 40, and after step 2 the previous state was 40, not off. Undo must restore the captured previous state, not a fixed opposite. Equally wrong is capturing prevBrightness in the constructor instead of inside execute(): a queued command constructed now but run later would record stale state. Capture at execute time.
Macro commands and queuing — same mechanism, more leverage
Because a command is just an object with execute()/undo(), a command can contain other commands. A MacroCommand runs its children forward and undoes them in reverse order (LIFO), so the world unwinds exactly as it was built:
class MacroCommand implements Command {
private final List<Command> cmds;
MacroCommand(List<Command> cmds) { this.cmds = cmds; }
public void execute() { for (Command c : cmds) c.execute(); }
public void undo() {
ListIterator<Command> it = cmds.listIterator(cmds.size());
while (it.hasPrevious()) it.previous().undo(); // reverse order
}
}
// "Movie night" = dim lights to 10, TV on, blinds down — one undoable unit.The same object also serializes naturally onto a queue: an invoker can push commands into a BlockingQueue and a worker thread drains and runs them later. That is the backbone of job queues, thread pools, and write-ahead logs — the request outlives the call stack that created it, and replaying the log replays the work. Reverse-order undo is load-bearing: undoing a macro front-to-back can restore an earlier step's state and then clobber it with a later step's stale inverse.
Macro atomicity: all-or-nothing execute()
The naive MacroCommand.execute() above has a critical gap: if child 2 of 3 throws during execute(), children 0 and 1 have already mutated the world, but the macro is neither done nor undone. The invoker cannot push a half-applied macro onto the undo stack (calling undo() on it would try to reverse child 2, whose execute() never completed — at best a no-op, at worst a second exception or corrupted state).
A macro is a mini-transaction, so it needs all-or-nothing semantics. Track how far you got and roll back on failure:
class MacroCommand implements Command {
private final List<Command> cmds;
MacroCommand(List<Command> cmds) { this.cmds = new ArrayList<>(cmds); }
public void execute() {
int done = 0;
try {
for (; done < cmds.size(); done++) {
cmds.get(done).execute();
}
} catch (RuntimeException e) {
// undo only the children that actually ran (0..done-1), in reverse
for (int i = done - 1; i >= 0; i--) {
cmds.get(i).undo();
}
throw e; // re-throw so the invoker never pushes a half-applied macro
}
}
public void undo() {
// all children succeeded, reverse them all
for (int i = cmds.size() - 1; i >= 0; i--) {
cmds.get(i).undo();
}
}
}Why this works
The done counter tracks how many children completed successfully. On failure, the catch block reverses only indices 0..done-1 — exactly the commands whose execute() finished — in reverse order, restoring the world to its pre-macro state. The re-throw ensures the invoker sees the failure and never records a partial macro on the undo stack.
Traced failure scenario
| Step | Child | Action | Outcome | done counter |
|---|---|---|---|---|
| 1 | cmds[0] (DimCmd) | execute() | success — brightness 100→10 | 1 |
| 2 | cmds[1] (TVOnCmd) | execute() | throws RuntimeException | 1 (not incremented) |
| 3 | cmds[0] (DimCmd) | undo() | brightness 10→100 (restored) | — |
| 4 | — | re-throw | invoker sees exception, macro never pushed to undo stack | — |
Why-NOT: when this guard is insufficient
This in-memory rollback only works when every child's undo() is reliable and side-effect-free against external systems. If child 0 sent an email or charged a credit card, calling undo() cannot unsend that email. For commands with irreversible external side effects, you need a two-phase approach: first validate all children (dry-run / precondition check), then execute — or use a real transaction coordinator (saga pattern with compensating actions). The in-memory rollback shown here is correct for in-process state mutations like UI operations, document edits, and game state — the sweet spot of the Command pattern.
Alternative: the Saga pattern
In distributed systems, the equivalent problem — "step 3 of 5 failed, roll back steps 1–2" — is solved by the Saga pattern, where each step has an explicit compensating action (not a generic undo()). The macro rollback above is essentially a local, synchronous saga. The trade-off: a saga handles cross-service failures and can compensate asynchronously, but requires explicit compensation logic per step and deals with eventual consistency; the in-macro rollback is simpler and atomic but only works in-process.
Pitfalls
- Undo restores a fixed opposite instead of captured state. The classic light-switch bug above. Always snapshot the receiver state you are about to overwrite, and restore that.
- Capturing state too early. Record previous state inside
execute(), not in the constructor — otherwise queued or re-executed commands undo to the wrong value. - Forgetting to clear the redo stack on a new action. Skip this and "redo" replays commands from an abandoned timeline, corrupting state.
- Macro half-mutation on child failure. If a MacroCommand runs 3 children and child 2 throws, children 0–1 have already mutated the world. Without a tracked rollback (
donecounter + reverse undo of completed children + re-throw), the macro leaves inconsistent state and a laterundo()callsundo()on a command whoseexecute()never completed. Treat macros as mini-transactions — all-or-nothing. - Commands holding heavy snapshots. If a command stores a full document copy for undo, a long history balloons memory. Prefer storing the minimal delta (the
prevBrightnessint, not the whole light), or use the Memento pattern for genuinely large state. - Non-idempotent receivers + retries. When commands are queued and a worker retries on failure, an
execute()with side effects (charge card, send email) can fire twice. Make execution idempotent or dedupe by command id. - Class explosion. One class per trivial action is a lot of ceremony. In languages with lambdas/functional interfaces, a
Commandcan often just be aRunnable/closure — reserve full classes for commands that needundo()or serialization.
When to use it / when NOT to
Reach for Command when you see any of these signals: you need undo/redo or a transactional "do these N things or none"; requests must be queued, scheduled, logged, or replayed (job queues, write-ahead logs, macros); or many heterogeneous UI triggers (button, menu, shortcut, voice) must fire the same action through one uniform invoker. The common thread: the request needs a lifetime independent of the call that issues it.
Trade-offs vs the alternatives
- vs. a direct method call / lambda. A plain call or a
Runnablegets you decoupling and "do later" for free, with zero extra classes. What it does not give you isundo(), introspection ("what is this command, can I serialize it?"), or a reversible history. Choose Command (full object) when you need undo, serialization, or a queryable history; prefer a lambda/Runnable when you only need deferred or polymorphic execution. - vs. Strategy. Both wrap behavior in an object, but the intent differs: Strategy swaps how one ongoing operation is done (which sort, which pricing rule) and is typically stateless and not reversible; Command represents a request to do something that you may store, queue, and reverse. Choose Command when the verb is the thing you collect/replay/undo; prefer Strategy when you are picking an algorithm to plug into a host.
- vs. Memento (for undo). Command undo stores the inverse action (cheap when deltas are small); Memento stores a snapshot of receiver state (simpler when computing an inverse is hard, but memory-heavy). Real editors often combine them: command history for the operations, mementos for steps whose inverse is impractical.
Costs you accept: more classes/indirection, an extra hop that complicates step-through debugging, and memory growth in long histories. If you have no undo, no queue, and no need to treat actions as data, Command is over-engineering — call the method.
Takeaways
- Command reifies a method call into an object so it can be stored, queued, logged, and reversed — the invoker speaks only the
Commandinterface and the receiver never learns undo exists. - Undo lives in
undo()+ an invoker history stack; each command must capture (at execute time) the minimal previous state needed to invert itself, not a hard-coded opposite. - Redo is a second stack that any fresh action must clear; macros compose commands and must undo children in reverse order.
- A macro is a mini-transaction: if any child fails mid-execute, roll back only the children that completed (tracked by a
donecounter), in reverse, then re-throw — never push a half-applied macro onto the undo stack. - Use it for undo/redo, queues, and replayable logs; for plain deferred execution a lambda is cheaper, and for hard-to-invert state Memento snapshots pair well. For cross-service rollback (irreversible side effects), graduate to the Saga pattern.
Sources: Gamma, Helm, Johnson & Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software (the original Command write-up, including MacroCommand and the undo discussion); Freeman & Robson, Head First Design Patterns, 2nd ed. (the remote-control / undo example this page builds on); Refactoring.Guru, "Command." Re-authored and deepened for this guide — added the dimmable-light undo/redo trace, the two-stack invoker implementation, the macro/queue mechanism, the "why the naive undo is wrong" note, the Command vs Strategy vs Memento selection guidance, and the macro atomicity (all-or-nothing execute with rollback) section including the Saga pattern alternative.
Interview drills & operability signals
Operability: a command bus in production needs two signals — a command_handler_failures counter (executions that threw) and a dead-letter queue for commands that fail every retry, so a poison command doesn't silently wedge the worker. The correctness smell to alert on: an undo() that doesn't restore the receiver's invariants, and a non-idempotent execute() that double-applies under retry.
- Q. Command vs. event? A command is an intent to change state that the handler may still reject (validation, authorization); an event is an immutable fact that something already happened. You queue commands to do work; you publish events to notify. This is why commands carry undo and events do not.
- Q. Design undo for a
MoveFilecommand.execute()renames A→B and stores both paths;undo()renames B→A only if the file is still at B (guard against a later command having moved it again). This is the same "capture what you need to invert, restore captured state" rule as the dimmable light — the inverse of a move is not "move somewhere," it is "move back to the exact captured source." - Q. Your
execute()makes a network call with no idempotency key — what breaks? A retry (worker crash after the call but before the ack) double-applies the side effect. Fix: attach an idempotency key (the command id) so the downstream dedupes, or makeexecute()/undo()intrinsically safe to repeat.
🤖 Don't fully get this? Learn it with Claude
Stuck on Command Pattern? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.
Build the mental picture, not memorization.
I just read a lesson on **Command Pattern** (OO & Low-Level Design) and want to truly understand it. Explain Command Pattern from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Socratic — adapts to where you're stuck.
Teach me **Command Pattern** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Active recall exposes what you missed.
Quiz me on **Command Pattern** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Intuition + hook + flashcards for long-term memory.
Help me remember **Command Pattern** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.