Behavioral Pattern Pattern 14 of 23

Command

Think of a universal remote control. Each button doesn't contain wiring straight to a specific lamp or TV — it holds a small, swappable "instruction" that knows how to turn that device on. You can reprogram a button to control a different device without touching the remote's internals, and some remotes even let you "undo" the last action. The Command pattern brings that idea into code: wrap a request as a standalone object with an execute() method, so it can be stored, passed around, queued, logged, or reversed — instead of just happening immediately as a function call.

⚠️ The Problem

Say you're building a text editor and want undo/redo. If typing text just calls document.insertText(str) directly, that call happens and is gone — there's nothing left afterward that remembers what was inserted, where, or how to reverse it. To support undo you'd need to track ad hoc state for every different kind of action (typing, deleting, formatting), and a plain function call gives you no natural place to store "how do I undo this" alongside "how do I do this."

✅ The Solution

Turn each action into a Command object with an execute() method (and usually an undo() method) that stores everything it needs to perform — or reverse — that one action. A TypeCommand remembers what text it inserted and where, so its undo() can remove exactly that text. The editor keeps a history stack of executed commands; undo just pops the last command and calls its undo(). The part of the code that triggers actions (a keypress handler, a button) only ever calls command.execute() — it doesn't know or care what the command actually does.

Example 1 — a text editor's undo/redo stack

Primary Example — JavaScript
class TypeCommand {
  constructor(document, text, position) {
    this.document = document;
    this.text = text;
    this.position = position;
  }

  execute() {
    this.document.insert(this.text, this.position);
  }

  undo() {
    this.document.remove(this.position, this.text.length);
  }
}

class DeleteCommand {
  constructor(document, position, length) {
    this.document = document;
    this.position = position;
    this.deletedText = document.content.slice(position, position + length);
  }

  execute() {
    this.document.remove(this.position, this.deletedText.length);
  }

  undo() {
    this.document.insert(this.deletedText, this.position);
  }
}

class TextDocument {
  content = "";

  insert(text, position) {
    this.content = this.content.slice(0, position) + text + this.content.slice(position);
  }

  remove(position, length) {
    this.content = this.content.slice(0, position) + this.content.slice(position + length);
  }
}

class Editor {
  history = [];
  doc = new TextDocument();

  run(command) {
    command.execute();
    this.history.push(command);
  }

  undo() {
    const command = this.history.pop();
    if (command) command.undo();
  }
}

const editor = new Editor();
editor.run(new TypeCommand(editor.doc, "Hello", 0));
editor.run(new TypeCommand(editor.doc, " World", 5));
console.log(editor.doc.content); // "Hello World"

editor.undo();
console.log(editor.doc.content); // "Hello"

The Editor never has special-case logic for "typing" versus "deleting" — it just calls execute() and pushes the command onto a stack, and undo just pops and calls undo(). Adding a new kind of action (like a formatting command) means writing one more class, with no change to Editor itself.

Example 2 — a secondary context: a smart home remote

Secondary Example — Java
interface Command {
    void execute();
    void undo();
}

class Light {
    private final String room;
    Light(String room) { this.room = room; }
    void on()  { System.out.println(room + " light: ON"); }
    void off() { System.out.println(room + " light: OFF"); }
}

class LightOnCommand implements Command {
    private final Light light;
    LightOnCommand(Light light) { this.light = light; }
    public void execute() { light.on(); }
    public void undo()    { light.off(); }
}

class LightOffCommand implements Command {
    private final Light light;
    LightOffCommand(Light light) { this.light = light; }
    public void execute() { light.off(); }
    public void undo()    { light.on(); }
}

class RemoteButton {
    private Command command;
    void assign(Command command) { this.command = command; }
    void press() { command.execute(); }
}

public class Demo {
    public static void main(String[] args) {
        Light kitchenLight = new Light("Kitchen");
        RemoteButton button = new RemoteButton();

        button.assign(new LightOnCommand(kitchenLight));
        button.press(); // Kitchen light: ON

        button.assign(new LightOffCommand(kitchenLight));
        button.press(); // Kitchen light: OFF
    }
}

RemoteButton has exactly one method, press(), and it works identically no matter which command object was assigned to it — a light, a thermostat, a garage door. Reprogramming a button at runtime is just swapping which Command object it holds.

👍 When To Use It

  • You need undo/redo — a Command object is the natural place to store what's needed to reverse an action, alongside the action itself.
  • You want to queue, log, schedule, or serialize actions to run later or replay — a Command object can be stored and executed whenever needed, unlike an immediate function call.
  • You want to decouple the thing that triggers an action (a button, a menu item, an API endpoint) from the code that actually performs it — the trigger only needs to call execute().

👎 When NOT To Use It

  • When an action is simple, immediate, and never needs to be undone, queued, or logged — a direct function call is clearer and has far less ceremony.
  • When wrapping every action in its own class would multiply the number of small, single-purpose classes far more than the benefit justifies — this is a real cost worth weighing on smaller codebases.
  • When undo is genuinely hard to express as "reverse this one command" — some actions (like sending an email) aren't reversible at all, and forcing an undo() method onto them is misleading.

❓ FAQ

Is Command the same thing as just passing a callback function?

They're closely related — both let you pass "an action" around as a value instead of calling it immediately. The difference is that a Command object can carry state and multiple related methods (execute() and undo(), for example), while a plain callback is usually just one function with no natural place to attach an inverse operation or extra metadata.

How is undo actually implemented — does every command need a perfect inverse?

Not always a perfect mathematical inverse, but the command does need to know how to restore prior state. Two common approaches: compute the inverse operation directly (as in the delete/undo example above), or have the command snapshot enough state before executing that undo() can just restore that snapshot — the second approach overlaps with the Memento pattern, covered later in this track.

Can a Command represent more than one action bundled together?

Yes — a "macro command" holds a list of other commands and calls execute() on each in order (and undo() on each in reverse order for a clean rollback). This is exactly how "record a macro" features work in many applications: each recorded step is a Command, and playback just replays the list.

🙋 There Are No Dumb Questions

Q: Doesn't this create a lot of tiny classes for what could just be a function call?
A: For simple one-off actions, yes, and that's a fair reason to skip the pattern. Command earns its cost specifically when you need to treat "doing something" as a first-class value — to store it, delay it, undo it, or log it — not just to make a single immediate call slightly more formal.

✅ Quick Check

What's the key benefit of wrapping an action in a Command object instead of calling a function directly?


Next up: what if you need to evaluate expressions in a small custom language or notation — arithmetic, simple rule conditions — over and over, without hand-writing ad hoc parsing logic scattered everywhere? That's the Interpreter pattern.

← Back to
Next →