Command
Encapsulates a request as an object, decoupling whoever invokes the action from whoever executes it — and enabling queuing, logging, undoing and redoing operations through the same interface.
Intent
Represent an action as a self-contained object that
encapsulates the operation, the receiver, and all the parameters
needed to execute it and, optionally, undo it. The object that
triggers the action (Invoker) only knows the Command
interface — it doesn't know who executes it or how.
Cataloged by the GoF (1994) as a behavioral pattern, Command shows up in editing systems (undo/redo), task queues, transactions, application menus, and processing pipelines. In modern architectures it's present in action dispatch (Redux/Flux), event sourcing, and automation systems where actions need to be persisted, replayed, or reverted.
Problem
Imagine a text editor where toolbar buttons perform operations on the document. The direct approach couples each button to the editor and prevents any undo mechanism:
// Naive approach — DON'T do this:
class InsertButton {
constructor(private editor: TextEditor) {}
click(text: string, position: number): void {
// Direct coupling to the editor. No history, no undo.
this.editor.insert(text, position);
}
}
class DeleteButton {
constructor(private editor: TextEditor) {}
click(position: number, length: number): void {
// Same: every button needs to know the editor's internal API.
this.editor.delete(position, length);
}
}
Immediate problems: there's no way to implement Ctrl+Z because there's nowhere to store "what was done and how to revert it"; every button or keyboard shortcut duplicates the invocation logic; adding new action types requires creating more components coupled to the editor. Testing also becomes harder because it's not possible to test the action history independently.
Command solves this by turning each action into an immutable object that carries the receiver, the parameters, and the state needed to undo itself. The Invoker keeps a stack of these objects, and implementing undo/redo becomes trivial — without modifying a single button, shortcut, or domain component.
Solution
Command organizes the code into four participants:
-
Command (interface): declares
execute()and, when undo is needed,undo(). Every concrete command implements this interface — the Invoker only knows this contract. -
ConcreteCommand: captures the Receiver and the
needed parameters in its constructor.
execute()delegates to the Receiver;undo()reverts the operation, usually by saving the prior state duringexecute()itself. -
Receiver: the object that knows how to perform the
actual operation — the "muscle" of the pattern. E.g.:
TextEditorwithinsert()anddelete()methods. The Receiver contains the domain logic; the Command only orchestrates it. -
Invoker: triggers the command via
execute()and keeps the history. It doesn't know the concrete types — only theCommandinterface. It implements undo/redo by walking the command stack.
The separation between Invoker and Receiver is the central benefit: a
UI button, a keyboard shortcut, a REST API and an automation script
can all invoke the same Command with zero knowledge of
the TextEditor.
Structure
«interface»
Command
┌───────────────────────────────┐
│ + execute(): void │
│ + undo(): void │
└───────────────────────────────┘
▲
┌──────────┴──────────────────────┐
│ │
InsertTextCommand DeleteTextCommand
(ConcreteCommand) (ConcreteCommand)
│ │
│ uses │ uses
▼ ▼
TextEditor (Receiver)
┌──────────────────────────────────────┐
│ + insert(text, position): void │
│ + delete(position, length): void │
│ + getContent(): string │
└──────────────────────────────────────┘
CommandHistory (Invoker)
┌──────────────────────────────────────────┐
│ - history: Command[] │
│ - index: number │
│ + run(cmd: Command): void │
│ + undo(): boolean │
│ + redo(): boolean │
└──────────────────────────────────────────┘
│ calls execute() / undo()
▼
Command
Typical flow (undo):
history.run(new InsertTextCommand(editor, "Hi", 0))
→ InsertTextCommand.execute()
→ editor.insert("Hi", 0) → content: "Hi"
history.undo()
→ InsertTextCommand.undo()
→ editor.delete(0, 2) → content: ""
Code examples
Example 1 — Text editor with undo/redo history
A complete implementation with the Command interface,
two concrete commands that capture the state needed to undo
themselves, the Receiver (TextEditor) and the Invoker
(CommandHistory) with full undo and redo support. Note
how DeleteTextCommand saves the removed text during
execute() — essential for a correct undo.
// ── Command interface ─────────────────────────────────────────
interface Command {
execute(): void;
undo(): void;
}
// ── Receiver ──────────────────────────────────────────────────
class TextEditor {
private content: string = '';
insert(text: string, position: number): void {
this.content =
this.content.slice(0, position) + text + this.content.slice(position);
}
delete(position: number, length: number): void {
this.content =
this.content.slice(0, position) +
this.content.slice(position + length);
}
getContent(): string { return this.content; }
}
// ── ConcreteCommands ──────────────────────────────────────────
class InsertTextCommand implements Command {
constructor(
private readonly editor: TextEditor,
private readonly text: string,
private readonly position: number
) {}
execute(): void {
this.editor.insert(this.text, this.position);
}
// Undo: deletes exactly the text that was inserted.
undo(): void {
this.editor.delete(this.position, this.text.length);
}
}
class DeleteTextCommand implements Command {
// State captured during execute() — essential for a correct undo.
private removedText: string = '';
constructor(
private readonly editor: TextEditor,
private readonly position: number,
private readonly length: number
) {}
execute(): void {
// Save the text BEFORE removing it.
this.removedText = this.editor
.getContent()
.slice(this.position, this.position + this.length);
this.editor.delete(this.position, this.length);
}
// Undo: reinserts the saved text at the original position.
undo(): void {
this.editor.insert(this.removedText, this.position);
}
}
// ── Invoker ───────────────────────────────────────────────────
class CommandHistory {
private readonly history: Command[] = [];
private index: number = -1;
run(command: Command): void {
// Discards the history ahead of the cursor (drops any pending redo).
this.history.splice(this.index + 1);
this.history.push(command);
this.index++;
command.execute();
}
undo(): boolean {
if (this.index < 0) return false;
this.history[this.index].undo();
this.index--;
return true;
}
redo(): boolean {
if (this.index >= this.history.length - 1) return false;
this.index++;
this.history[this.index].execute();
return true;
}
}
// ── Usage ────────────────────────────────────────────────────
const editor = new TextEditor();
const history = new CommandHistory();
history.run(new InsertTextCommand(editor, 'Hi', 0));
console.log(editor.getContent()); // 'Hi'
history.run(new InsertTextCommand(editor, ' there', 2));
console.log(editor.getContent()); // 'Hi there'
history.run(new DeleteTextCommand(editor, 2, 6));
console.log(editor.getContent()); // 'Hi'
history.undo();
console.log(editor.getContent()); // 'Hi there'
history.undo();
console.log(editor.getContent()); // 'Hi'
history.redo();
console.log(editor.getContent()); // 'Hi there'
<?php
// ── Command interface ─────────────────────────────────────────
interface Command
{
public function execute(): void;
public function undo(): void;
}
// ── Receiver ──────────────────────────────────────────────────
class TextEditor
{
private string $content = '';
public function insert(string $text, int $position): void
{
$this->content =
substr($this->content, 0, $position)
. $text
. substr($this->content, $position);
}
public function delete(int $position, int $length): void
{
$this->content =
substr($this->content, 0, $position)
. substr($this->content, $position + $length);
}
public function getContent(): string
{
return $this->content;
}
}
// ── ConcreteCommands ──────────────────────────────────────────
class InsertTextCommand implements Command
{
public function __construct(
private readonly TextEditor $editor,
private readonly string $text,
private readonly int $position
) {}
public function execute(): void
{
$this->editor->insert($this->text, $this->position);
}
public function undo(): void
{
$this->editor->delete($this->position, strlen($this->text));
}
}
class DeleteTextCommand implements Command
{
private string $removedText = '';
public function __construct(
private readonly TextEditor $editor,
private readonly int $position,
private readonly int $length
) {}
public function execute(): void
{
// Save the text BEFORE removing it — needed for undo.
$this->removedText = substr(
$this->editor->getContent(),
$this->position,
$this->length
);
$this->editor->delete($this->position, $this->length);
}
public function undo(): void
{
$this->editor->insert($this->removedText, $this->position);
}
}
// ── Invoker ───────────────────────────────────────────────────
class CommandHistory
{
/** @var Command[] */
private array $history = [];
private int $index = -1;
public function run(Command $command): void
{
array_splice($this->history, $this->index + 1);
$this->history[] = $command;
$this->index++;
$command->execute();
}
public function undo(): bool
{
if ($this->index < 0) return false;
$this->history[$this->index]->undo();
$this->index--;
return true;
}
public function redo(): bool
{
if ($this->index >= count($this->history) - 1) return false;
$this->index++;
$this->history[$this->index]->execute();
return true;
}
}
// ── Usage ────────────────────────────────────────────────────
$editor = new TextEditor();
$history = new CommandHistory();
$history->run(new InsertTextCommand($editor, 'Hi', 0));
echo $editor->getContent() . "\n"; // Hi
$history->run(new InsertTextCommand($editor, ' there', 2));
echo $editor->getContent() . "\n"; // Hi there
$history->run(new DeleteTextCommand($editor, 2, 6));
echo $editor->getContent() . "\n"; // Hi
$history->undo();
echo $editor->getContent() . "\n"; // Hi there
$history->undo();
echo $editor->getContent() . "\n"; // Hi
$history->redo();
echo $editor->getContent() . "\n"; // Hi there
Example 2 — Remote control with MacroCommand
Command also allows composing multiple commands into a single
MacroCommand — executing and undoing a set of actions as
if they were an atomic unit. This demonstrates the pattern's
composability: a MacroCommand is a Command that contains other
Commands, applying undo in reverse order.
// Command interface (same as Example 1).
interface Command {
execute(): void;
undo(): void;
}
// ── Receiver: devices ─────────────────────────────────────────
class Light {
private on = false;
private brightness = 100;
turnOn(): void { this.on = true; console.log(`Light on (${this.brightness}%)`); }
turnOff(): void { this.on = false; console.log('Light off'); }
setBrightness(pct: number): void {
const previous = this.brightness;
this.brightness = pct;
console.log(`Brightness: ${previous}% → ${pct}%`);
}
getBrightness(): number { return this.brightness; }
}
// ── ConcreteCommands ──────────────────────────────────────────
class TurnOnLightCommand implements Command {
constructor(private readonly light: Light) {}
execute(): void { this.light.turnOn(); }
undo(): void { this.light.turnOff(); }
}
class DimmerCommand implements Command {
private previousBrightness = 0;
constructor(
private readonly light: Light,
private readonly newBrightness: number
) {}
execute(): void {
this.previousBrightness = this.light.getBrightness();
this.light.setBrightness(this.newBrightness);
}
undo(): void {
this.light.setBrightness(this.previousBrightness);
}
}
// MacroCommand: composes several Commands into a single Command object.
class MacroCommand implements Command {
constructor(private readonly commands: Command[]) {}
execute(): void {
this.commands.forEach(c => c.execute());
}
// Undo in reverse order — undoes the last subcommand first.
undo(): void {
[...this.commands].reverse().forEach(c => c.undo());
}
}
// ── Invoker: remote control with an undo stack ────────────────
class RemoteControl {
private readonly stack: Command[] = [];
press(command: Command): void {
command.execute();
this.stack.push(command);
}
undoLast(): void {
const last = this.stack.pop();
last?.undo();
}
}
// ── Usage ────────────────────────────────────────────────────
const light = new Light();
const remote = new RemoteControl();
remote.press(new TurnOnLightCommand(light));
// Light on (100%)
remote.press(new DimmerCommand(light, 40));
// Brightness: 100% → 40%
remote.undoLast();
// Brightness: 40% → 100%
// Macro: "movie mode" — turns on and dims as a single action.
const movieMode = new MacroCommand([
new TurnOnLightCommand(light),
new DimmerCommand(light, 10),
]);
remote.press(movieMode);
// Light on (100%)
// Brightness: 100% → 10%
// Undoes the whole macro in reverse order.
remote.undoLast();
// Brightness: 10% → 100%
// Light off
<?php
// Command interface (same as Example 1).
interface Command
{
public function execute(): void;
public function undo(): void;
}
// ── Receiver ─────────────────────────────────────────────────
class Light
{
private bool $on = false;
private int $brightness = 100;
public function turnOn(): void
{
$this->on = true;
echo "Light on ({$this->brightness}%)\n";
}
public function turnOff(): void
{
$this->on = false;
echo "Light off\n";
}
public function setBrightness(int $pct): void
{
$previous = $this->brightness;
$this->brightness = $pct;
echo "Brightness: {$previous}% → {$pct}%\n";
}
public function getBrightness(): int
{
return $this->brightness;
}
}
// ── ConcreteCommands ──────────────────────────────────────────
class TurnOnLightCommand implements Command
{
public function __construct(private readonly Light $light) {}
public function execute(): void { $this->light->turnOn(); }
public function undo(): void { $this->light->turnOff(); }
}
class DimmerCommand implements Command
{
private int $previousBrightness = 0;
public function __construct(
private readonly Light $light,
private readonly int $newBrightness
) {}
public function execute(): void
{
$this->previousBrightness = $this->light->getBrightness();
$this->light->setBrightness($this->newBrightness);
}
public function undo(): void
{
$this->light->setBrightness($this->previousBrightness);
}
}
class MacroCommand implements Command
{
/** @param Command[] $commands */
public function __construct(private readonly array $commands) {}
public function execute(): void
{
foreach ($this->commands as $c) {
$c->execute();
}
}
public function undo(): void
{
foreach (array_reverse($this->commands) as $c) {
$c->undo();
}
}
}
// ── Invoker ───────────────────────────────────────────────────
class RemoteControl
{
/** @var Command[] */
private array $stack = [];
public function press(Command $command): void
{
$command->execute();
$this->stack[] = $command;
}
public function undoLast(): void
{
$last = array_pop($this->stack);
$last?->undo();
}
}
// ── Usage ────────────────────────────────────────────────────
$light = new Light();
$remote = new RemoteControl();
$remote->press(new TurnOnLightCommand($light));
// Light on (100%)
$remote->press(new DimmerCommand($light, 40));
// Brightness: 100% → 40%
$remote->undoLast();
// Brightness: 40% → 100%
$movieMode = new MacroCommand([
new TurnOnLightCommand($light),
new DimmerCommand($light, 10),
]);
$remote->press($movieMode);
// Light on (100%)
// Brightness: 100% → 10%
$remote->undoLast();
// Brightness: 10% → 100%
// Light off
When to use
- When you need undo/redo: Command captures everything needed to revert an operation — the central use case of the pattern in editors, spreadsheets, and any interactive editing system.
- When actions need to be queued or scheduled: task queues, schedulers, and retry systems store Command objects for later or repeated execution — without needing to know what each one does.
- For auditing and action logs: each Command is a structured record of the action, its parameters, and its timestamp. In financial or compliance systems, this provides an auditable history of operations.
- To decouple the UI from the domain: buttons, keyboard shortcuts, and REST APIs can all invoke the same Commands without knowing anything about the Receiver — the "what to do" logic is centralized in the Command.
- To compose actions into macros or transactions using MacroCommand — a set of operations that must be executed and undone as an atomic unit.
When to avoid
- When the action is simple, with no undo and no queue: creating an interface and a class per action for something like "print report" or "send e-mail" with no need to revert is overengineering. A direct call or a closure is enough.
- When class explosion becomes unmanageable: systems with 50 action types generate 50 Command classes. In TypeScript, closures or first-class functions are a viable alternative for simple Commands with no undo state.
- When undo is infeasible or irrelevant: operations like "send SMS" or "debit bank account" have irreversible side effects. Implementing real undo in these cases would require complex compensations (refund, cancellation message) — and that's usually business-domain concern, not a design-pattern one.
Pros and cons
Pros
- Decouples the Invoker from the Receiver — whoever triggers the action doesn't need to know how it's carried out.
- Undo/redo is implemented in the Invoker, without modifying any UI or domain component.
- Makes task queues, audit logs, retries, and scheduling of operations easier.
- MacroCommand allows composing atomic operations without changing existing Commands.
- Follows the Open/Closed Principle — new action types don't require modifying the Invoker.
Cons
- Increases the number of classes — one class per action type can grow quickly.
- For correct undo, each Command needs to capture and manage prior state — which can be complex for operations that affect multiple objects.
- The Invoker can accumulate memory with an unbounded history — a depth limit needs to be defined.
- For simple, stateless Commands, creating a full class is more verbose than a closure or function.
Common pitfalls
1. Anemic command (just forwards the call)
The most common mistake when learning the pattern: creating a
Command that encapsulates nothing beyond a direct call to the
Receiver, without capturing parameters in the constructor and
without implementing real undo. A SaveCommand that just
calls repository.save(externalObject) without fixing
the object in the constructor isn't a Command — it's useless
boilerplate.
Rule of thumb: if the Command doesn't capture
everything it needs to execute and undo autonomously, it's
anemic. The constructor should fix the Receiver + parameters;
execute() shouldn't receive arguments.
2. Poorly implemented undo (state not captured)
The most critical technical pitfall: implementing
execute() without capturing the prior state needed for
undo(). A DeleteTextCommand that removes
text without first saving it in this.removedText
permanently loses the ability to revert. Golden rule: capture
everything undo() will need before performing
the destructive operation.
3. Command class explosion
In systems with many action types, the number of Command classes grows proportionally to the actions. When that happens and the Commands have no undo state, closures are more idiomatic. In TypeScript:
// Functional alternative for simple, stateless Commands:
type SimpleCommand = () => void;
const queue: SimpleCommand[] = [];
queue.push(() => console.log('Task A'));
queue.push(() => console.log('Task B'));
queue.forEach(cmd => cmd());
// Reserve Command classes for when undo, logging, or composition are needed.
4. Command vs Strategy — the intent distinction
The most frequent confusion between these two patterns. Command encapsulates a specific action with a defined lifecycle: creation → execution → (possible) undo. The object represents "what to do and with what data" — it can be stored, queued, and reverted. Strategy encapsulates an interchangeable algorithm with no identity or lifecycle — the context swaps the active strategy, but doesn't store strategies as a history. Use Command when the action itself is a first-class piece of data; use Strategy when only the algorithm's variation matters.
Related patterns
Command interacts with patterns that complement undo, queuing, and coordination of actions:
Strategy and Command both encapsulate behavior in objects, but with distinct intents: Strategy swaps interchangeable algorithms in the context (no execute/undo lifecycle); Command represents discrete actions that can be stored, queued, and reverted. Mediator can use Commands to represent the coordinated actions between components — the Mediator receives a Command and decides which Receiver to forward it to, without the colleagues knowing each other directly. Memento is the natural complement to Command for complex undo: when the Receiver's state is too rich to be captured in each individual Command, Memento creates a complete snapshot of the object before the operation, and the Command simply stores that snapshot to restore it on undo.