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:
Define a command interface — usually one method, execute(), plus undo() when you need it.
Each concrete command captures, at construction, everything the operation needs: the receiver, the parameters, and whatever it must remember to reverse itself.
The invoker — a button, a queue, a scheduler, a retry loop — holds commands and calls execute(). It knows nothing about documents or bold text.
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.
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)).
from dataclasses import dataclass, field
from typing import Protocol
class Command(Protocol):
def execute(self) -> None: ...
def undo(self) -> None: ...
@dataclass
class DeleteCommand:
document: Document
start: int
end: int
_removed: str = field(default="", init=False)
def execute(self) -> None:
self._removed = self.document.text[self.start:self.end]
self.document.delete(self.start, self.end)
def undo(self) -> None:
self.document.insert(self.start, self._removed)
class Editor:
def __init__(self) -> None:
self._done: list[Command] = []
self._undone: list[Command] = []
def run(self, command: Command) -> None:
command.execute()
self._done.append(command)
self._undone.clear()
def undo(self) -> None:
if self._done:
command = self._done.pop()
command.undo()
self._undone.append(command)
# When you only need execute(), a closure or functools.partial IS the pattern,
# and a class would be noise:
#
# queue.append(partial(document.delete, 0, 5))
#
# The moment you need undo, an audit log or serialisation, you need somewhere to
# put the captured state — and that's when it has to become a class again.
#
# Celery is this pattern over a network: @task turns a call into a serialisable
# message that a different machine executes later.
#include <deque>
#include <memory>
#include <string>
class Command {
public:
virtual ~Command() = default;
virtual void execute() = 0;
virtual void undo() = 0;
};
class DeleteCommand final : public Command {
public:
DeleteCommand(Document& doc, std::size_t from, std::size_t to)
: doc_(doc), from_(from), to_(to) {}
void execute() override {
removed_ = doc_.textBetween(from_, to_); // capture before mutating
doc_.erase(from_, to_);
}
void undo() override { doc_.insert(from_, removed_); }
private:
Document& doc_;
std::size_t from_, to_;
std::string removed_;
};
class Editor {
public:
void run(std::unique_ptr<Command> c) {
c->execute();
done_.push_back(std::move(c));
undone_.clear();
}
void undo() {
if (done_.empty()) return;
done_.back()->undo();
undone_.push_back(std::move(done_.back()));
done_.pop_back();
}
private:
std::deque<std::unique_ptr<Command>> done_, undone_;
};
// Qt ships the pattern under its own name: QUndoCommand with redo()/undo(),
// pushed onto a QUndoStack that gives you the undo history UI for free.
// If you never need undo, std::function<void()> is the whole pattern.
public interface ICommand
{
void Execute();
void Undo();
}
public sealed class DeleteCommand(Document document, int from, int to) : ICommand
{
private string _removed = "";
public void Execute()
{
_removed = document.TextBetween(from, to);
document.Delete(from, to);
}
public void Undo() => document.Insert(from, _removed);
}
public sealed class Editor
{
private readonly Stack<ICommand> _done = new();
private readonly Stack<ICommand> _undone = new();
public void Run(ICommand command)
{
command.Execute();
_done.Push(command);
_undone.Clear();
}
public void Undo()
{
if (_done.TryPop(out var command)) { command.Undo(); _undone.Push(command); }
}
}
// .NET has the pattern in the framework: System.Windows.Input.ICommand is the
// backbone of MVVM, with Execute, CanExecute and CanExecuteChanged — that
// CanExecute is a genuinely useful addition GoF didn't have, because it lets
// the invoker (a button) grey itself out without knowing what the command does.
const deleteCommand = (document, from, to) => {
let removed = '' // captured on execute, used by undo
return {
name: 'delete',
execute() {
removed = document.text.slice(from, to)
document.delete(from, to)
},
undo() {
document.insert(from, removed)
},
}
}
const editor = {
done: [],
undone: [],
run(command) {
command.execute()
this.done.push(command)
this.undone.length = 0
},
undo() {
const command = this.done.pop()
if (!command) return
command.undo()
this.undone.push(command)
},
}
// Redux is this pattern at application scale, with one important variation: an
// ACTION is a serialisable command object ({ type, payload }), the reducer is
// the receiver, and undo is not implemented per-command at all — the store
// keeps previous STATES instead. That's Command for the request and Memento for
// the reversal, which is a very common and very sensible combination.
package editor
// Go has no classes, but the pattern is unchanged: an interface with the
// operations, and structs that carry the captured parameters.
type Command interface {
Execute()
Undo()
}
type DeleteCommand struct {
Doc *Document
From, To int
removed string // unexported: nobody outside sets this
}
func (c *DeleteCommand) Execute() {
c.removed = c.Doc.TextBetween(c.From, c.To)
c.Doc.Delete(c.From, c.To)
}
func (c *DeleteCommand) Undo() { c.Doc.Insert(c.From, c.removed) }
type Editor struct{ done, undone []Command }
func (e *Editor) Run(c Command) {
c.Execute()
e.done = append(e.done, c)
e.undone = e.undone[:0]
}
func (e *Editor) Undo() {
if len(e.done) == 0 {
return
}
c := e.done[len(e.done)-1]
e.done = e.done[:len(e.done)-1]
c.Undo()
e.undone = append(e.undone, c)
}
// Note the pointer receivers. A value receiver would mutate a COPY, so
// `removed` would be empty by the time Undo ran — the single most common way
// to get this pattern wrong in Go.
//
// The worker-queue idiom `jobs := make(chan func())` is Command reduced to its
// minimum: a request as a value, sent somewhere else to be run.
interface Command {
readonly name: string
execute(): void
undo(): void
}
class DeleteCommand implements Command {
readonly name = 'delete'
#removed = ''
constructor(
private readonly document: Document,
private readonly from: number,
private readonly to: number,
) {}
execute(): void {
this.#removed = this.document.textBetween(this.from, this.to)
this.document.delete(this.from, this.to)
}
undo(): void {
this.document.insert(this.from, this.#removed)
}
}
// For commands that cross a process boundary — a queue, a websocket, an event
// log — model them as a serialisable union instead of classes, so the wire
// format and the handler are checked against each other:
type Action =
| { type: 'insert'; at: number; text: string }
| { type: 'delete'; from: number; to: number }
| { type: 'bold'; from: number; to: number }
const apply = (doc: Document, action: Action): void => {
switch (action.type) {
case 'insert': return doc.insert(action.at, action.text)
case 'delete': return doc.delete(action.from, action.to)
case 'bold': return doc.bold(action.from, action.to)
}
// Adding a fourth action type makes this switch a compile error — which is
// exactly what you want when commands are persisted and replayed later.
}
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
Define the command interface: execute(), plus undo() only if you genuinely need reversal.
Give each command everything it needs at construction — receiver and parameters — so it never reads live state when it runs.
Capture whatever undo requires before mutating: the deleted text, the previous value, the old formatting.
Write the invoker so it knows only the interface. If your queue or toolbar mentions a document, the seam is in the wrong place.
Keep two stacks for undo and redo, and clear the redo stack whenever a new command runs.
If commands are queued or persisted, make them serialisable and idempotent — a retried command must not double-charge anyone.
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…
Use
Because
Turn a request into an object you can store, queue and reverse
Command
The request becomes a value with its own state.
Restore an object's whole state rather than reverse one action
A 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?
A command has to be a complete description of an action fixed in time, and this one isn't — it's a description of "delete whatever happens to be selected when you get round to it". Straight from the toolbar the two are the same, which is why the bug hides. Put it in a queue, retry it after a failure, or replay it from an audit log and the selection is somewhere else entirely; redo after undo is the same problem, since the user's cursor moved while they were undoing. Capture from and to in the constructor and the command becomes portable across all four of those situations.
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.
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.