Command

Turn a request into a standalone object. This lets you parameterize methods with different requests, delay execution, queue operations, and implement undoable actions.

The Problem

You’re building a text editor. The toolbar has 20 buttons, the menu has 40 items, and there are keyboard shortcuts. Many of these trigger the same operations. You end up with: a CopyButton that calls editor.copy(), a CopyMenuItem that calls the same thing, a keyboard handler that does the same. Every new feature duplicates this wiring. And when you want to add undo/redo, you need to track history — but the history has to live somewhere, and each individual button/menu isn’t the right place.


The Solution

Encapsulate each request as a Command object: an object with an execute() method (and optionally undo()). Buttons and menu items hold a command reference — they don’t know what the command does, just that they should call execute(). The history stack stores command objects — undo calls undo() in reverse.

Invoker - history: Command[] + executeCommand() «interface» Command + execute(): void CopyCommand + execute() PasteCommand + execute() Receiver + copy() + paste()
Command — Invoker holds Command objects; ConcreteCommands encapsulate Receiver calls for undo/redo

Real-World Analogy

A restaurant order. The waiter (invoker) takes your order (command) and passes it to the kitchen (receiver). The order is a self-contained object: it doesn’t need the waiter, it doesn’t need to know which chef is on duty. Orders can be queued, delayed, or cancelled before being prepared. The chef executes orders without knowing who placed them.


TypeScript Example

// Command interface
interface Command {
  execute(): void;
  undo(): void;
  description: string;
}

// Receiver — the thing that knows how to do actual work
class TextEditor {
  private text = '';
  private cursor = 0;

  insertText(text: string, at: number): void {
    this.text = this.text.slice(0, at) + text + this.text.slice(at);
    this.cursor = at + text.length;
  }

  deleteText(at: number, length: number): void {
    this.text = this.text.slice(0, at) + this.text.slice(at + length);
    this.cursor = at;
  }

  getText(): string { return this.text; }
  getCursor(): number { return this.cursor; }
}

// Concrete commands
class InsertCommand implements Command {
  readonly description: string;

  constructor(
    private readonly editor: TextEditor,
    private readonly text: string,
    private readonly at: number,
  ) {
    this.description = `Insert "${text}" at ${at}`;
  }

  execute(): void {
    this.editor.insertText(this.text, this.at);
  }

  undo(): void {
    this.editor.deleteText(this.at, this.text.length);
  }
}

class DeleteCommand implements Command {
  readonly description: string;
  private deletedText = '';

  constructor(
    private readonly editor: TextEditor,
    private readonly at: number,
    private readonly length: number,
  ) {
    this.description = `Delete ${length} chars at ${at}`;
  }

  execute(): void {
    // Capture the deleted text before deleting (for undo)
    this.deletedText = this.editor.getText().slice(this.at, this.at + this.length);
    this.editor.deleteText(this.at, this.length);
  }

  undo(): void {
    this.editor.insertText(this.deletedText, this.at);
  }
}

// Macro Command — composite: executes many commands as one
class MacroCommand implements Command {
  readonly description: string;

  constructor(
    private readonly commands: Command[],
    description: string,
  ) {
    this.description = description;
  }

  execute(): void { this.commands.forEach(c => c.execute()); }
  undo(): void   { [...this.commands].reverse().forEach(c => c.undo()); }
}

// Invoker — manages history, triggers commands
class EditorController {
  private history: Command[] = [];
  private future: Command[]  = [];

  execute(command: Command): void {
    command.execute();
    this.history.push(command);
    this.future = []; // clear redo stack on new action
    console.log(`  ✓ ${command.description}`);
  }

  undo(): void {
    const cmd = this.history.pop();
    if (!cmd) { console.log('  Nothing to undo'); return; }
    cmd.undo();
    this.future.push(cmd);
    console.log(`  ↩ Undid: ${cmd.description}`);
  }

  redo(): void {
    const cmd = this.future.pop();
    if (!cmd) { console.log('  Nothing to redo'); return; }
    cmd.execute();
    this.history.push(cmd);
    console.log(`  ↪ Redid: ${cmd.description}`);
  }
}

// Demo
const editor     = new TextEditor();
const controller = new EditorController();

console.log('--- Editing ---');
controller.execute(new InsertCommand(editor, 'Hello', 0));
controller.execute(new InsertCommand(editor, ' World', 5));
controller.execute(new InsertCommand(editor, '!', 11));
console.log('Text:', editor.getText()); // → Hello World!

console.log('\n--- Undo ×2 ---');
controller.undo();
controller.undo();
console.log('Text:', editor.getText()); // → Hello

console.log('\n--- Redo ---');
controller.redo();
console.log('Text:', editor.getText()); // → Hello World

When to Use

  • You want to parameterize objects with operations (e.g. toolbar buttons with actions)
  • You need to queue, delay, or schedule operations
  • You need undoable/redoable operations
  • You need a transaction system where multiple operations must succeed or all roll back
  • Examples: text editors, drawing apps, game input systems, task queues, database migrations

Pros

  • Single Responsibility — decouple classes that invoke operations from classes that perform them
  • Open/Closed — add new commands without changing existing invokers or receivers
  • Enables undo/redo by storing command history
  • Commands are first-class objects — queue them, log them, serialize them
  • Assemble macro commands from simpler ones (Composite pattern)

Cons

  • Increases the number of classes — one class per command type
  • Can be overkill for simple, non-undoable operations

  • Chain of Responsibility passes requests through a chain; Command encapsulates a request as an object
  • Memento works with Command to implement undo — Command performs changes, Memento stores state for rollback
  • Strategy is similar — both encapsulate behaviour — but Commands represent operations (often undoable), Strategies represent interchangeable algorithms
  • Composite can be used to build macro commands from individual commands