CMD Guide
HomeOO & Low-Level DesignBehavioral

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

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.

diagram
diagram

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.

StepActionCommand runbrightnessundo stack (top→)redo stack
0start0[][]
1press OnOnCmd: prev=0, set 4040[OnCmd][]
2press Dim 70DimCmd: prev=40, set 7070[DimCmd, OnCmd][]
3undopop DimCmd → undo(): set prev=4040[OnCmd][DimCmd]
4undopop OnCmd → undo(): set prev=00[][DimCmd, OnCmd]
5redopop OnCmd → execute(): set 4040[OnCmd][DimCmd]
6press On againOnCmd2: prev=40, set 40; redo cleared40[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

StepChildActionOutcomedone counter
1cmds[0] (DimCmd)execute()success — brightness 100→101
2cmds[1] (TVOnCmd)execute()throws RuntimeException1 (not incremented)
3cmds[0] (DimCmd)undo()brightness 10→100 (restored)
4re-throwinvoker 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

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

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


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.

🔨 Practice this hands-on — Design a Text Editor with Undo/Redo →
Attempt it from an empty file, break it to feel the failure, then defend it under pushback.
🤖 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.

🎨 Explain it visually

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.
🤔 Walk me through it (interactive)

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.
🧪 Quiz me & fix my gaps

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.
🧠 Make it stick

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.

📝 My notes