Free Interactive Course · Design Patterns

Command Design Pattern

Turn a request into an object — then you can queue it, log it, retry it, and undo it.

Behavioural Patternsmediuma.k.a. Actiona.k.a. Transaction
ShareXLinkedIn
In one sentence

Encapsulate a request as an object, letting you parameterise clients with different requests, queue or log them, and support undoable operations.

01

The problem Command solves

The problem

Your editor's toolbar calls methods directly: the bold button calls document.applyBold(selection), the delete key calls document.deleteRange(from, to). Simple, readable, and it works.

Then the requirements arrive, one at a time, and every one of them is impossible.

Undo. The document has already changed and nothing remembers what it looked like, or what the user did, or in what order. A macro recorder. There is no "what the user did" to record — only method calls that have already happened. Queue the expensive operations. You cannot put a method call in a queue. An audit log of every change. You'd have to add a log line to forty methods and keep them in sync forever. Retry the one that failed. Retry what, exactly?

Each of those needs the same thing you don't have: the request itself, as a value — something you can hold, store, count, reverse and hand to someone else.

02

How the Command pattern works

Stop calling the method. Build an object that represents calling the method, and hand that to whoever should run it:

  1. Define a command interface — usually one method, execute(), plus undo() when you need it.
  2. Each concrete command captures, at construction, everything the operation needs: the receiver, the parameters, and whatever it must remember to reverse itself.
  3. The invoker — a button, a queue, a scheduler, a retry loop — holds commands and calls execute(). It knows nothing about documents or bold text.
  4. Because the request is now an object, everything else falls out for free: push it on an undo stack, put it in a queue, serialise it to a log, replay it, batch several into one.
Capture the state at construction, not at execution. A command that reads "the current selection" when it runs will do the wrong thing the moment it's queued, retried or replayed — because by then the selection has moved. The command must be a complete, self-contained description of an action fixed in time. This one rule is the difference between a command system that survives contact with a queue and one that produces baffling bugs under load.
Toolbarthe invoker«interface» Commandexecute() · undo()BoldCommandrange, wasBoldDeleteCommandrange, removedTextDocumentthe receiverholdsacts onUndoStack · Queue · AuditLog · Retryonce the request is an object, the bottom row costs almost nothing to add
Participants. The Invoker (toolbar, queue, scheduler) holds commands and triggers them. Each Concrete Command binds a Receiver to a set of arguments. The Client creates commands and decides who receives them. Everything in the bottom row — undo, queueing, audit, retry — exists only because the request became a value.
03

See it: execute, then take it back

Step through a short editing session. Each step pushes a command onto the undo stack; each undo pops one and calls its undo(). Watch the document state, and watch what each command had to remember in order to reverse itself.

▶ Try it — an editing session with undo

An interactive undo stack: executing InsertCommand, BoldCommand and DeleteCommand changes the document one step at a time, and each undo calls that command's inverse operation using the state it captured when it was created.

Look at what each command remembers. DeleteCommand keeps the text it removed; BoldCommand keeps whether the range was already bold. That captured state is the whole cost of undo in this pattern — and it's the difference from Memento, which doesn't reverse anything and instead restores a whole snapshot of the document.
04

Command pattern code examples

One interface, an undo stack, and commands that capture everything they need up front.

public interface Command {
    void execute();
    void undo();
}

public final class DeleteCommand implements Command {

    private final Document document;   // the receiver
    private final int from, to;        // parameters, fixed at construction
    private String removed;            // what it must remember to reverse itself

    public DeleteCommand(Document document, int from, int to) {
        this.document = document;
        this.from = from;
        this.to = to;
    }

    @Override public void execute() {
        removed = document.textBetween(from, to);   // capture BEFORE changing
        document.delete(from, to);
    }

    @Override public void undo() {
        document.insert(from, removed);
    }
}

/** The invoker. Note what it doesn't know: anything about documents. */
public final class Editor {
    private final Deque<Command> done = new ArrayDeque<>();
    private final Deque<Command> undone = new ArrayDeque<>();

    public void run(Command c) {
        c.execute();
        done.push(c);
        undone.clear();      // a new action invalidates the redo branch
    }

    public void undo() { if (!done.isEmpty()) { Command c = done.pop(); c.undo(); undone.push(c); } }
    public void redo() { if (!undone.isEmpty()) { Command c = undone.pop(); c.execute(); done.push(c); } }
}

// `Runnable` is Java's minimal Command, which is why an ExecutorService can
// queue work it knows nothing about: executor.submit(new DeleteCommand(doc, 0, 5)).
Read across the tabs: when a command only needs execute(), every one of these languages already has it — Runnable, std::function, a closure, a func() on a channel — and writing a class would be ceremony. The class earns its place the moment you need somewhere to put captured state: for undo, for an audit log, or to serialise the request and run it on another machine. Two framework versions are worth knowing by name: .NET's ICommand adds CanExecute so a button can disable itself, and Qt's QUndoCommand hands you an entire undo-history UI for free.
05

How to implement Command

  1. Define the command interface: execute(), plus undo() only if you genuinely need reversal.
  2. Give each command everything it needs at construction — receiver and parameters — so it never reads live state when it runs.
  3. Capture whatever undo requires before mutating: the deleted text, the previous value, the old formatting.
  4. Write the invoker so it knows only the interface. If your queue or toolbar mentions a document, the seam is in the wrong place.
  5. Keep two stacks for undo and redo, and clear the redo stack whenever a new command runs.
  6. If commands are queued or persisted, make them serialisable and idempotent — a retried command must not double-charge anyone.
  7. Group related commands into a composite command when several changes should undo as one user-visible step.
06

When to use Command — and when not to

Use it when you need undo or redo, when operations must be queued, scheduled or retried, when you want an audit log or event history of what was requested, when a macro recorder or scripting layer needs to replay user actions, or when the thing issuing a request must not know what carries it out.

Where it goes wrong

A class per click. If all you need is "run this later", a lambda or function value already is the pattern. Twenty command classes with a single execute() and no captured state are ceremony, not design.

Reading live state at execution time. A command that asks for "the current selection" when it runs breaks the instant it's queued or replayed. Bind the arguments when you build it.

Undo that isn't the inverse. Undoing a delete by inserting the text at the same offset is wrong if something else moved in between. Undo must restore the state the command changed, and for anything non-trivial that usually means a snapshot rather than an inverse.

Undoing the un-undoable. Sending an email, charging a card, deleting an S3 object. The honest design is a compensating action (a refund, a tombstone) and a clear rule about which commands are reversible — not an undo() that silently does nothing.

Retries without idempotency. The moment commands go in a queue, "at least once" delivery is the default. A ChargeCard command without an idempotency key will eventually charge someone twice.

You want to…UseBecause
Turn a request into an object you can store, queue and reverseCommandThe request becomes a value with its own state.
Restore an object's whole state rather than reverse one actionMementoMemento snapshots state; Command reverses an operation. They combine well.
Swap the algorithm a method usesStrategyStrategy answers "how"; Command answers "what, and when".
Give several objects a chance to handle a requestChain of ResponsibilityCoR routes the request; Command is the request.
Treat a group of commands as one commandCompositeA composite command undoes several changes as a single user-visible step.
07

Quick check

🧠 Quick check
Your DeleteCommand.execute() reads editor.currentSelection() to decide what to delete. The command works perfectly from the toolbar. What breaks first?

In the wild

C#System.Windows.Input.ICommand — the backbone of MVVM, with CanExecute letting a button disable itself without knowing what the command does.
C++Qt's QUndoCommand and QUndoStack, which implement this pattern by name and give you the undo-history UI along with it.
JavaRunnable submitted to an ExecutorService — a request as a value, queued and run elsewhere; Swing's Action adds enabled state and a label.
JavaScriptRedux — an action is a serialisable command object and the reducer is the receiver; time-travel debugging works because the requests are values.
PythonCelery tasks: @task turns a function call into a serialisable message another machine executes later — Command across a network boundary.
GoThe worker-queue idiom, jobs := make(chan func()) — Command reduced to its minimum, a request as a value sent somewhere else to run.

Frequently asked questions

What is the Command pattern used for?
Anything that needs a request to exist as a value rather than as a method call that already happened: undo and redo, job and task queues, retries, macro recording, audit logs and event sourcing, scheduling, and remote execution. The tell is a requirement phrased in the past or future tense — "undo what I just did", "run this later", "replay what happened" — because none of those are possible when the request only ever existed as a call on the stack.
What is the difference between Command and Strategy?
Both put behaviour in an object, and the difference is what the object represents. A Strategy is an interchangeable algorithm the caller picks to change how an operation is done — sort with this comparator, compress with that codec. A Command is a complete request, bound to its receiver and arguments, that can be stored, queued and reversed. Strategies are usually stateless and passed in; commands carry state and are usually kept.
How does Command implement undo?
Each command records what it needs to reverse itself, then undo() applies the inverse — DeleteCommand saves the removed text and re-inserts it, BoldCommand saves whether the range was already bold. The invoker keeps a stack of executed commands, pops one on undo and pushes it to a redo stack. Where the inverse is hard or the state is large, the usual fix is to store a Memento in the command instead of an inverse operation.
Is a lambda or a function pointer a Command?
For the simple case, yes, and you should use it. A closure captures its arguments and can be stored, queued and passed around, which is everything GoF's execute() asks for — Runnable, std::function, func() and a JavaScript arrow function are all commands. You need the full object when you need somewhere to keep extra state: an undo(), a canExecute(), a name for the audit log, or a serialisable form to send over a queue.
How do you undo something that can't be undone, like sending an email?
You don't — you compensate. Model the reversal as its own action with its own real-world meaning: a refund for a charge, a correction email, a tombstone record for a delete. Then be explicit in the design about which commands are reversible, so the UI can hide undo where it would be a lie. An undo() that silently does nothing is much worse than no undo button, because the user believes it worked.

Finish the handbook, earn the certificate

All 23 Gang-of-Four patterns, each with a simulator you can click and code in seven languages. Free, no signup.

See all 23 patterns →
Found this course useful? Share it.
ShareXLinkedIn

Comments

0

Join the conversation. Sign in to leave a comment — we'd love to hear your thoughts.