What Is the Command Pattern?
The Command pattern encapsulates a request — an action plus all the parameters needed to perform it — as a standalone object with a uniform execute() method, so the code that triggers an action (the invoker) is decoupled from the code that knows how to perform it (the receiver). This turns operations into first-class objects that can be queued, logged, passed around, or undone, instead of being hardwired as direct method calls.
Cricket analogy: A DRS review request is a Command object: the on-field umpire (invoker) doesn't need to know how the third umpire (receiver) actually analyzes Hawk-Eye data, it just calls execute() by raising the TV signal.
Structure: Invoker, Command, Receiver
The Command interface declares an execute() method (and often an undo() method); each ConcreteCommand stores a reference to a Receiver plus whatever parameters the action needs, and implements execute() by calling the appropriate method(s) on that receiver. The Invoker holds a Command reference and calls execute() on it without knowing which ConcreteCommand or Receiver is behind it, which is what lets you swap a 'light on' button to control a fan or a garage door just by changing which Command object is bound to it.
Cricket analogy: A remote-control fielding buzzer (Invoker) just presses a button; the ConcreteCommand behind it knows to signal the twelfth man (Receiver) to bring out gloves, and swapping the buzzer's wiring to instead signal a drinks break changes only the Command, not the buzzer.
interface Command {
execute(): void;
undo(): void;
}
class TextDocument {
content = '';
insert(text: string, at: number): void {
this.content = this.content.slice(0, at) + text + this.content.slice(at);
}
delete(at: number, length: number): void {
this.content = this.content.slice(0, at) + this.content.slice(at + length);
}
}
class InsertTextCommand implements Command {
constructor(private doc: TextDocument, private text: string, private at: number) {}
execute(): void {
this.doc.insert(this.text, this.at);
}
undo(): void {
this.doc.delete(this.at, this.text.length);
}
}
class CommandHistory {
private stack: Command[] = [];
execute(command: Command): void {
command.execute();
this.stack.push(command);
}
undoLast(): void {
const command = this.stack.pop();
command?.undo();
}
}
const doc = new TextDocument();
const history = new CommandHistory();
history.execute(new InsertTextCommand(doc, 'Hello', 0));
history.undoLast();Undo/Redo and Command Queues
Because a Command object captures everything needed to perform an action, it can also capture everything needed to reverse it, which is why text editors, drawing tools, and database migration frameworks implement undo() alongside execute() and keep an executed-commands stack; popping that stack and calling undo() on the top command reverts the last operation cleanly. The same encapsulation lets commands be queued and executed later — asynchronously, in batch, or on a different thread — since a Command object is just data plus a method, with no dependency on the calling context that created it.
Cricket analogy: A DRS 'umpire's call' reversal works because the review system captured the exact ball-tracking data needed to re-adjudicate, the same way a Command's undo() needs the original state captured at execute() time.
Command's execute() and undo() forming a matched pair is exactly the idea behind database migration frameworks: an up() migration is a ConcreteCommand's execute(), and down() is its undo(), each capturing enough information to precisely reverse the other.
Command vs Strategy
Command and Strategy look structurally similar — both wrap behavior behind a single-method interface — but their intent differs: Strategy is about choosing which algorithm computes a result, and is usually stateless and interchangeable at any time, while Command is about capturing a request as an object, often carrying state specific to that one invocation (like undo history, timestamps, or a queue position), and is meant to be executed once, possibly logged, and possibly reversed.
Cricket analogy: Choosing between two run-chase calculation methods for required run-rate (Strategy) is stateless math, whereas a specific DRS review request tied to one exact ball bowled at 14.3 overs (Command) is a one-time, loggable, reversible event.
Not every Command needs undo(); a fire-and-forget action like sending an email log entry is a valid Command without a reverse operation. Only add undo() when the domain genuinely requires reversibility — implementing it speculatively adds complexity and state you may never use.
- Command encapsulates a request as an object with a uniform execute() method, decoupling the invoker from the receiver.
- The three participants are Invoker, Command (interface plus ConcreteCommand), and Receiver.
- Because a Command captures the full context of a request, it can also implement undo() to reverse it.
- An executed-commands stack lets applications support multi-level undo/redo cleanly.
- Commands are just data plus a method, so they can be queued, logged, or executed asynchronously.
- Command differs from Strategy in intent: Strategy picks a stateless algorithm, Command captures one discrete, often-reversible invocation.
- Not every Command needs undo(); add it only when the domain requires reversibility.
Practice what you learned
1. What does the Command pattern encapsulate as an object?
2. In the Command pattern, which participant knows how to actually perform the requested action?
3. Why can Command objects support undo() naturally, while a typical direct method call cannot?
4. How does Command differ from Strategy in typical intent?
5. A photo-editing app needs a history panel that lets a user step backward and forward through their edits (crop, rotate, filter). Which capability of the Command pattern directly supports this?
Was this page helpful?
You May Also Like
Strategy Pattern
The Strategy pattern encapsulates interchangeable algorithms behind a common interface so a context object can swap behavior at runtime without conditional logic.
State Pattern
The State pattern lets an object change its behavior when its internal state changes by delegating to interchangeable State objects that manage their own transitions.
Observer Pattern
The Observer pattern lets a Subject notify a dynamic list of Observers automatically whenever its state changes, decoupling event producers from consumers.
Related Reading
Related Study Notes in Software Engineering
Browse all study notesMicroservices Study Notes
Software Architecture · 30 topics
Software EngineeringTesting & TDD Study Notes
Software Testing · 30 topics
Software EngineeringSoftware Engineering Study Notes
Python · 40 topics
Software EngineeringGit & Version Control Study Notes
Bash · 40 topics
Software EngineeringSystem Design Study Notes
Architecture · 40 topics