Command Pattern: Turn an Action into Data

Command packages a request as an object so actions can be queued, logged, retried, composed, or reversed independently from the caller.

What is the Command pattern?

The Command pattern is a behavioral design pattern that turns a request into a stand-alone object carrying everything needed to perform it — the action, its parameters, and a reference to its receiver. Once an action is data, infrastructure can do things to it that are impossible with a plain method call: queue it, log it, retry it, schedule it, compose it into macros, and undo it.

The pattern splits three roles that usually blur together: the client that creates a command, the invoker that decides when it runs (a button, a queue worker, a history stack), and the receiver that does the actual work. The invoker's ignorance is the feature — an undo stack can manage ten thousand different operations because it sees nothing but execute() and undo().

The logic, step by step

Problem

A caller knows too much about how an operation executes, making undo, scheduling, and retry behavior difficult to add.

Rule

Represent each request behind an execute contract and store everything required to perform or reverse that request.

Run
  1. CALLER creates a command describing the requested operation.
  2. INVOKER queues, records, or executes the command without domain details.
  3. COMMAND delegates to its receiver and may preserve state for undo.
Output

Operations become first-class values that infrastructure can manage without understanding the domain action.

Command pattern example in TypeScript

TypeScript
interface Command {
  execute(): void;
  undo(): void;
}

class InsertText implements Command {
  constructor(private doc: TextDocument, private at: number, private text: string) {}
  execute(): void { this.doc.insert(this.at, this.text); }
  undo(): void { this.doc.delete(this.at, this.text.length); }
}

class DeleteRange implements Command {
  private removed = '';
  constructor(private doc: TextDocument, private at: number, private len: number) {}
  execute(): void { this.removed = this.doc.delete(this.at, this.len); }
  undo(): void { this.doc.insert(this.at, this.removed); }
}

class History {
  private done: Command[] = [];
  run(cmd: Command): void { cmd.execute(); this.done.push(cmd); }
  undoLast(): void { this.done.pop()?.undo(); }
}

const history = new History();
history.run(new InsertText(doc, 0, 'Hello'));
history.run(new DeleteRange(doc, 0, 5));
history.undoLast(); // "Hello" is back — DeleteRange remembered what it removed
History manages any command without knowing what it does. Note DeleteRange capturing the removed text during execute — commands carry the state their own undo needs.

Run it in the real world

An editor stores insert, delete, and format commands in history so the toolbar can execute them and the user can undo them later.

When to use it — and when not to

Reach for Command when

  • You need undo/redo — Command is essentially the only clean way to build it.
  • Operations must be queued, persisted, scheduled, or retried by infrastructure that cannot know domain details.
  • UI triggers (buttons, shortcuts, menu items) should share one action implementation.

Skip it when

  • Calls are fire-and-forget with no lifecycle needs — wrapping them in objects is ceremony without payoff.
  • True undo is impossible anyway (sent emails, external side effects); a command object cannot fix that.

Strategy pattern — Strategy encapsulates how something is done and a context holds one at a time; Command encapsulates that something should be done and invokers can hold thousands (queues, histories, macros).

Observer pattern — Observer notifies whoever happens to listen; Command targets a known receiver. Events describe the past ("order placed"), commands request a future ("place order").

Frequently asked questions

How does the Command pattern implement undo?

Each command either stores an inverse operation or snapshots the state it is about to change during execute(). A history stack then pops commands and calls undo() in reverse order. Redo is a second stack fed by undone commands.

What is the difference between a command and an event?

A command is an instruction addressed to one handler and can be rejected ("PlaceOrder"). An event is a fact that already happened, broadcast to any number of subscribers ("OrderPlaced"). CQRS systems keep the two vocabularies strictly separate.

Is the Command pattern still relevant in functional languages?

Yes, it just loses the class syntax. A closure is a command; a queue of closures is an invoker. What functions alone do not give you is the undo() half — pairing do/undo still needs a structure, whether a class or a record of two functions.