Beginner12 min readDesign Patternslive prototype

Command

Turn a request into a stand-alone object. Instead of a button flipping the light directly, pressing it creates a `LightOnCommand` object that wraps the action and the device it acts on. Because the action is now an *object*, you can store it on a history stack, undo it, queue it for later, or log it — the remote never needs to know what any button actually does.

The idea

What it is

Command turns a request into a stand-alone object — so instead of calling an action, you create an object that represents the action, and execute it whenever (and wherever) you like. In one sentence: wrap a request as an object so you can parameterize callers with it, queue it, log it, and undo it. Think of a smart-home remote: pressing a button doesn't flip the light directly. The press creates a LightOnCommand object that bundles what to do (turn on) with what to do it to (the living-room light). The remote just says execute() — it has no idea whether that means a light, a thermostat, or the garage door.

The classic analogy is a restaurant order slip. You tell the waiter what you want, and the waiter doesn't cook — they write your request on a slip and hand it to the kitchen. That slip is a command object: it captures the request, it can sit in a queue on the kitchen rail, the chef picks it up when ready, it can be cancelled, and at the end of the night the stack of slips is a log of everything that happened. The moment a request becomes a physical thing rather than a fleeting function call, all of that becomes possible.

The one sentence to remember

Command makes an action a first-class object with an execute() (and usually an undo()) method. Anything you can do with an object — store it, pass it around, put it on a stack, replay it — you can now do with an action.

The superpower is the history stack

Once every action is an object that knows how to reverse itself, undo is almost free: keep executed commands on a stack, and undo is just history.pop().undo(). This is exactly how editors, drawing apps, and IDEs implement Ctrl+Z.

Mechanics

How it works

The four players

The pattern splits 'a button press turns on a light' into four small roles. The split looks like ceremony at first, but each role is what unlocks one of the pattern's powers:

  • Command interface — the tiny contract every action signs: execute() and (for undoable systems) undo(). This is all the invoker ever sees.
  • Concrete commands — one small class per action: LightOnCommand, LightOffCommand, ThermostatUpCommand. Each one holds a reference to its receiver and knows how to perform and reverse exactly one request.
  • Receiver — the object that does the real work: the Light, the Thermostat. It has ordinary methods (turnOn(), up()) and knows nothing about commands at all.
  • Invoker — the remote button (or menu item, or job queue worker). It holds some command and calls execute() on it. It never knows the concrete class — swap which command a button holds and the button's behaviour changes without touching the button.

The client (your setup code) wires it all together: it creates the receivers, creates concrete commands around them, and hands those commands to the invoker. Notice how this echoes [[dependency-injection-and-ioc]] — the button is given its behaviour from outside rather than hard-coding it.

How undo works

Each command knows how to reverse itself: LightOnCommand.undo() turns the light back off; ThermostatUpCommand.undo() steps the temperature back down. The invoker keeps a history stack — every executed command gets pushed on top. Undo pops the most recent command and calls its undo(), which unwinds actions in exact reverse order. Redo, if you want it, is a second stack of undone commands. Real systems cap the stack (say, the last 100 actions), so the oldest commands eventually fall off and can no longer be undone — the prototype's 6-slot stack shows this honestly.

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

class LightOnCommand implements Command {
  constructor(private light: Light) {}   // wraps action + receiver
  execute() { this.light.turnOn(); }
  undo()    { this.light.turnOff(); }    // knows its own reverse
}

class RemoteButton {                     // invoker
  private history: Command[] = [];
  press(cmd: Command) {
    cmd.execute();                       // run the request...
    this.history.push(cmd);              // ...and remember it
  }
  undo() {
    this.history.pop()?.undo();          // reverse the latest one
  }
}

Look at what RemoteButton doesn't know: no if (button === 'light') branching, no reference to Light or Thermostat, no knowledge of what any command does. It just tells the command to execute — a clean example of [[tell-dont-ask]] at the object level. All the device-specific knowledge lives inside the concrete commands.

You've already met this pattern

  • Editor undo/redo — every keystroke, deletion, and format change is a command on a stack; Ctrl+Z pops and reverses it.
  • Job and task queues — a background job is a serialized command: created now, queued, executed later by a worker that only knows the run() interface.
  • Transactional operations — each step of a multi-step operation is a command with a compensating undo(), so a failure can roll everything back in reverse.
  • GUI buttons and menu items — 'Copy' in the toolbar, the Edit menu, and Ctrl+C all hold the same command object; one action, three invokers.
  • Macro recording — record the commands a user performs into a list, then replay the list to repeat the whole sequence.

Interactive prototype

See it. Build it. Break it.

A sandboxed, hands-on simulation — no setup, no install. Play with it as you read.

About this simulation

A smart-home remote wired up with the Command pattern. Press 💡 Light on, 💡 Light off, or 🌡 Heat + on the remote (left) and watch the two-hop: a command card (LightOnCommand, ThermostatUpCommand…) pops onto the history stack in the middle first, and only then does the device on the right react — the bulb lights up, the temperature ticks. That middle hop is the pattern: the button never touches the device; it builds an object that does. Now press ⎌ Undo — the top card pops off the stack and the device reverses (light back off, temp back down), unwinding in exact reverse order. The stack holds at most 6 commands (oldest drops off — a bounded undo depth, just like real editors). The history: N chip counts what's undoable.

Hands-on

Try these yourself

Open the prototype above, predict what happens, then verify.

try 01

Watch the two-hop

Press 💡 Light on once and watch closely: a LightOnCommand card pops onto the history stack in the middle first, and only then does the bulb on the right light up. The button never touched the light — it built an object, and the object did the work. Press 🌡 Heat + and watch a ThermostatUpCommand do the same for the thermostat.

try 02

Fill the stack

Mix presses — 💡 Light on, 🌡 Heat + a few times, 💡 Light off — and watch the history: N chip climb as cards pile on top. Keep going past six presses: the stack is full, so the oldest card drops off the bottom. That command can no longer be undone — you've just discovered why real editors have a bounded undo depth.

try 03

Unwind with Undo

Now press ⎌ Undo repeatedly. Each press pops the top card and reverses exactly that action — light back off, temperature back down — in perfect reverse order, until the stack is empty. Press ⎌ Undo once more on the empty stack: nothing breaks, there is simply nothing left to reverse. Hit ↺ Reset and replay any sequence you like.

In practice

When to use it — and what trips people up

Reach for Command when the request itself needs a life of its own

The tell-tale sign is that you need to do something with an action besides just running it right now:

  • Undo / redo — the flagship use. Keep executed commands on a stack; each knows how to reverse itself, so Ctrl+Z is a pop and an undo().
  • Queues and scheduling — create a request now, run it later (or elsewhere). Background jobs, thread pools, and task schedulers all pass command-shaped objects to workers that only know run().
  • Decoupling the trigger from the action — toolbar button, menu item, and keyboard shortcut all hold the same command object; UI elements become interchangeable holders of behaviour instead of hard-coding it.
  • Macros, logging, and replay — a list of commands is a recording. Replay it for macros, write it to disk as an audit log, or re-run it to recover state after a crash.

When NOT to use it

If a button just needs to call a method — no history, no queue, no undo, no swapping behaviour at runtime — then a plain direct call is better. Wrapping every method call in a command class is pure ceremony: you pay the class-per-action tax and get none of the powers. Add the pattern when a concrete need (undo, queueing, logging) shows up, not before.

What it gives you

  • Undo/redo nearly for free — each command reverses itself, so a history stack gives you Ctrl+Z with a pop and an undo() call.
  • Decouples invoker from receiver — the button knows only execute(), so you can rewire what a button does without touching the button, and reuse one command from many triggers.
  • Actions become data — commands can be queued, scheduled, serialized, logged, and replayed, which is impossible with a bare method call.
  • Easy to extend and compose — add a new action by adding one small class (open/closed), or build a macro command that runs a whole list of commands as one.

Common mistakes

  • Class proliferation — every distinct action becomes its own class, so a big app can accumulate dozens of tiny command classes (lambdas/closures soften this in modern languages).
  • Indirection tax — a simple 'call this method' now takes an interface, a concrete class, and an invoker; readers must hop through layers to find what actually happens.
  • Undo is only as correct as each command's undo() — reversing stateful or side-effecting actions (sent emails, API calls) can be hard or impossible, and one buggy undo corrupts the whole history.
  • History has real costs — storing commands (and any state snapshots they need for undo) consumes memory, which is why undo stacks are capped and old commands become unrecoverable.

Reference

Code & further reading

A minimal reference implementation and pointers worth bookmarking.

// Receivers — the devices that do the real work.
class Light {
  on = false;
  turnOn()  { this.on = true; }
  turnOff() { this.on = false; }
}
class Thermostat {
  temp = 20;
  up()   { this.temp++; }
  down() { this.temp--; }
}

// Command — every action signs this tiny contract.
interface Command {
  execute(): void;
  undo(): void;
}

// Concrete commands — wrap ONE action + its receiver.
class LightOnCommand implements Command {
  constructor(private light: Light) {}
  execute() { this.light.turnOn(); }
  undo()    { this.light.turnOff(); }     // its own reverse
}
class ThermostatUpCommand implements Command {
  constructor(private t: Thermostat) {}
  execute() { this.t.up(); }
  undo()    { this.t.down(); }
}

// Invoker — knows only the interface, keeps history for undo.
class Remote {
  private history: Command[] = [];
  press(cmd: Command) { cmd.execute(); this.history.push(cmd); }
  undo() { this.history.pop()?.undo(); }  // pop + reverse
}

// Client wires it together.
const light = new Light();
const remote = new Remote();
remote.press(new LightOnCommand(light));  // light.on === true
remote.undo();                            // light.on === false

References & further reading

5 sources

Knowledge check

Did it land?

Quick questions, answers revealed on submit. Sign in to save your best score.

question 01 / 05

What is the core intent of the Command pattern?

question 02 / 05

In the smart-home example, which object actually knows HOW to turn the light on?

question 03 / 05

How does undo typically work in the Command pattern?

question 04 / 05

The remote button holds a Command but never knows its concrete class. What does that buy you?

question 05 / 05

When is Command the WRONG choice?

0/5 answered