designpattern.site

Command Pattern

Encapsulate a request as an object to parameterize clients, queue operations, and support undoable actions.

Overview

Most code triggers actions directly: a button click calls editor.bold(), a menu item calls editor.paste(). This works fine at small scale, but it breaks down the moment you need to undo an action, replay a sequence of operations, schedule work for later, or log every user action to a file.

The Command pattern is a behavioral design pattern that solves this by wrapping each request in its own object. That object carries everything needed to perform the action — the receiver, the method to call, and the arguments — plus optionally the inverse logic needed to undo it.

The result is a clean separation: the code that decides what to do is decoupled from the code that knows how to do it. The command object bridges the two, and because it is a first-class value, it can be stored, queued, passed around, and reversed.

Problem & Motivation

Imagine you are building a text editor. You have a toolbar with Bold, Italic, and Undo buttons, a right-click context menu with the same actions, and keyboard shortcuts that do the same things. If each UI element calls editor methods directly, you face several problems:

  • Code duplication — Bold logic is written three times (toolbar, context menu, keyboard handler), and every new surface adds another copy.
  • No undo support — to reverse an action you need to save state before every call, and there is no consistent place to do that.
  • No history — you cannot replay what the user did, serialize it to disk, or show an audit trail.
  • Hard to extend — adding a macro feature (record and replay a sequence of actions) requires rewriting the caller side, not just the editor.

The Command pattern solves all four problems by making each action an object. Every UI element issues a command object. The editor executes it. The history stack stores it. Undo calls the command's reverse method.

Class Diagram

Loading diagram...

The key structural roles are:

  • Command — the interface declaring execute() and undo(). Every action implements this contract.
  • ConcreteCommand (BoldCommand, ItalicCommand) — holds a reference to the receiver and captures any state needed for undo.
  • Receiver (Editor) — the object that knows how to perform the real work.
  • Invoker (Application) — triggers commands without knowing what they do; also manages the history stack.
  • History — a stack of executed commands. Undo pops the top entry and calls undo() on it.

Implementation

The examples below model a minimal text editor where BoldCommand and ItalicCommand wrap text in markers, and the editor keeps a history stack so any operation can be reversed.

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

// Receiver — the object that does the real work
class Editor {
  text: string = "";

  wrapSelection(before: string, after: string): void {
    this.text = `${before}${this.text}${after}`;
  }

  unwrapSelection(before: string, after: string): void {
    if (this.text.startsWith(before) && this.text.endsWith(after)) {
      this.text = this.text.slice(before.length, this.text.length - after.length);
    }
  }
}

// Concrete commands
class BoldCommand implements Command {
  constructor(private editor: Editor) {}

  execute(): void {
    this.editor.wrapSelection("**", "**");
  }

  undo(): void {
    this.editor.unwrapSelection("**", "**");
  }
}

class ItalicCommand implements Command {
  constructor(private editor: Editor) {}

  execute(): void {
    this.editor.wrapSelection("_", "_");
  }

  undo(): void {
    this.editor.unwrapSelection("_", "_");
  }
}

// Invoker — manages execution and history
class Application {
  private history: Command[] = [];

  constructor(private editor: Editor) {}

  executeCommand(cmd: Command): void {
    cmd.execute();
    this.history.push(cmd);
  }

  undoLast(): void {
    const cmd = this.history.pop();
    if (cmd) cmd.undo();
  }
}

// Usage
const editor = new Editor();
editor.text = "Hello";

const app = new Application(editor);
app.executeCommand(new BoldCommand(editor));
console.log(editor.text);   // **Hello**

app.executeCommand(new ItalicCommand(editor));
console.log(editor.text);   // _**Hello**_

app.undoLast();
console.log(editor.text);   // **Hello**

app.undoLast();
console.log(editor.text);   // Hello

Undo State Snapshot vs. Inverse Operation

There are two strategies for undo. The first stores a full snapshot of the receiver's state before execute() and restores it on undo() — simple but memory-hungry for large objects. The second computes the mathematical inverse of each operation (e.g., remove the bold markers you just added) — efficient but only works when every operation has a clean inverse. The text editor example above uses the inverse approach. For complex domain objects, consider the Memento pattern to capture and restore state snapshots instead.

Real-World Examples

GUI frameworks — Every button, menu item, and keyboard shortcut in a desktop application is a command. Qt's QAction, Swing's Action interface, and WPF's ICommand all implement this pattern. A single SaveCommand instance can be registered on the toolbar button, the File menu entry, and the Ctrl+S shortcut simultaneously — no duplication.

Task queues and job schedulers — Systems like Celery, Sidekiq, and AWS SQS serialize command objects to a queue. Workers dequeue them and call execute(). The command encapsulates everything needed: the task type, its arguments, and retry metadata. Scheduling, retrying, and distributing work across machines becomes trivial.

Database transactions and migration tools — Flyway and Liquibase model each database migration as a command with an execute() (apply migration) and an undo() (rollback). The framework runs them in order and can reverse a specific migration without touching earlier ones.

Game engines — Turn-based games record every player action as a command. This allows replaying a match from the beginning, rolling back illegal moves, implementing a spectator mode, and serializing game state to disk. Unity's command-based input system follows the same principle.

Pros and Cons

Advantages

Decoupling — the sender (button, scheduler, API handler) knows nothing about the receiver. You can change either side without touching the other.

Undo and redo — once commands are objects stored in a history stack, undo is just pop() and undo(). Multi-level undo costs nothing extra.

Composability — a MacroCommand that contains a list of commands and calls execute() on each gives you transaction-like grouping for free.

Queueing and scheduling — commands can be serialized, sent over a network, persisted to a database, or delayed — because they are data, not live method calls.

Open/Closed — adding a new action means adding a new command class. Existing invokers and the history stack need no changes.

Disadvantages

Class proliferation — each distinct action becomes its own class. A feature-rich editor might have dozens of command classes, which increases the file count and navigation overhead.

Undo complexity — not every action has a clean inverse. Deleting a file, sending an email, or charging a credit card cannot be trivially undone. You must decide upfront which commands support undo and handle the others carefully.

Stale state in captured references — commands capture references to their receiver at construction time. If the receiver is mutated or destroyed before the command is executed (common in async scenarios), the command may operate on stale or invalid state.

Memory cost for deep history — storing every command in a history stack can be expensive if commands capture large snapshots of state. You may need to cap history depth or use compressed snapshots.

When to Use / When to Avoid

Use Command when:

  • You need undoable or redoable operations — the pattern was designed for this.
  • Multiple UI elements or entry points trigger the same action and you want a single place to manage it.
  • You need to queue, schedule, serialize, or log operations.
  • You want to build macro or batch operations from smaller primitives.
  • You are implementing a transactional system where operations must be atomic and reversible.

Avoid Command when:

  • The application has no need for undo, history, or deferred execution — the overhead of wrapping every action in a class is not justified.
  • Actions are purely stateless function calls with no receiver state to manage — a plain function reference or callback is simpler.
  • The receiver and invoker are in the same module and will never be separated — the indirection adds complexity without benefit.
  • You are working in a language with first-class functions and closures — a captured closure often serves the same purpose as a command class with far less ceremony.

Memento — the natural partner to Command for undo. When an operation's inverse is too complex or cannot be computed, Memento captures the receiver's full state before execute(). The command's undo() restores from the memento rather than computing a reverse.

Strategy — both patterns encapsulate behavior in an object. The difference is intent: Strategy varies how an algorithm runs (interchangeable at runtime), while Command varies what is requested (stored and replayed over time). A command is typically used once; a strategy is used repeatedly.

Chain of Responsibility — both decouple a sender from a handler. Chain of Responsibility passes the request along a handler chain until one accepts it; Command routes one request to one known receiver. They are sometimes combined — a command object travels through a responsibility chain before reaching its handler.

Composite — a MacroCommand that holds a list of child commands and delegates execute() and undo() to each is a direct application of the Composite pattern. This lets you treat a sequence of actions as a single undoable unit.

Design Tip

In modern JavaScript and Python, you will often see the Command pattern implemented as closures rather than classes. A function that returns a { execute, undo } object is functionally equivalent to a command class and requires far less boilerplate. Reserve full class-based commands for cases where you need serialization, type discrimination, or rich metadata attached to each operation.

On this page