codejump

AcademyDesign Patterns

Command pattern

Turning "do this" into an object that can be queued, logged or undone, the class every action now needs, and a command that reaches into global state instead of its own.

6 min readUpdated

3 exercises

Waiting at the end of this page.

Command is the pattern for treating a request as data instead of an immediate function call, which is what makes undo stacks and task queues possible in the first place.

A Command wraps "do this specific thing" as an object with an execute() method, instead of a direct function call. That turns a request into a value, one that can be handed to something else, queued, logged, or run later, without the thing invoking it needing to know what it does.

The problem it solves

A remote control has buttons. Each button should turn on a light, open a garage door, or change a channel. If the button code calls those devices directly, the button class knows about every device:

TypeScript
press() {
  if (this.mode === 'light') light.turnOn();
  else if (this.mode === 'garage') garage.open();
}
  • The invoker is coupled to what it triggers. Adding a device means editing the button.
  • Nothing can be undone. A direct call leaves no record of what happened.
  • Nothing can be delayed. The call happens now, or not at all. You cannot queue it, retry it or run it on another machine.

The fix is to put each action in its own object with an execute() method. The button holds a command and calls execute(), whatever it does.

Structure

The invoker only knows how to call execute(). The command knows which receiver to call and how
The invoker only knows how to call execute(). The command knows which receiver to call and how

Four parts:

  • The command (LightOnCommand): an object representing one action, with execute().
  • The receiver (Light): the object that does the real work.
  • The invoker (RemoteButton): triggers the command without knowing what it does.
  • The client, which creates commands and connects them to invokers.
class Light {
  turnOn() { return 'light-on'; }
}

class LightOnCommand {
  constructor(light) { this.light = light; }
  execute() { return this.light.turnOn(); }
}

class RemoteButton {
  constructor(command) { this.command = command; }
  press() { return this.command.execute(); }
}

A button can be given a different command at any time, and the same command can be triggered by a button, a keyboard shortcut or a voice assistant. The invoker and the receiver never meet.

Where it is used in real systems

Because actions are objects, they can be stored in a history for undo or placed in a queue to run later
Because actions are objects, they can be stored in a history for undo or placed in a queue to run later

Once a request is an object, three things become easy:

  • Undo and redo. Add an undo() method to each command, and keep executed commands on a stack.
  • Queues and scheduling. Store commands and run them later, in order, on a worker.
  • Logging and replay. Serialize commands, and you have an audit trail you can replay to rebuild state.
Use caseThe command
Text editorsBold, paste, delete, each with an undo().
Background jobsA job payload such as "send this email", processed by a worker (BullMQ, SQS).
Redux and event sourcingAn action object describing what happened, replayed to rebuild state.
Database migrationsEach migration has an up and a down.
Macros and shortcutsA list of commands run in sequence.
CLI toolsEach subcommand (git commit, npm install) is a command object.
TransactionsOperations collected, then applied together or rolled back.

Commands you already use

  • Redux actions. { type: 'cart/add', payload } is a command as data, dispatched to a reducer.
  • Job queues. A message in BullMQ or SQS is a serialized command that a worker executes later.
  • `Function.prototype.bind` and closures. () => light.turnOn() is a command in its lightest form.
  • VS Code and editor command palettes. Every entry is a named command that can be bound to a key.
  • Database migrations. Each one is a reversible command applied in order.

Command or just a function

In JavaScript a function is often enough. A command object earns its place when you need more than "call it now":

You needA plain functionA command object
Run it nowFineOverkill
Undo itAwkwardundo() on the same object
Store or send itClosures cannot be serializedPlain data, can be stored or sent
Log or inspect itOpaqueHas a name and parameters
Queue and retryPossibleNatural

If none of these apply, use a function.

When not to use it

  • A simple callback is enough. One-shot click handlers do not need command classes.
  • Too many tiny classes. One class per action can bloat a codebase. Group them, or use data plus a handler map.
  • Undo is hard to implement. Some actions, such as sending an email or charging a card, cannot truly be undone. A command's undo() for them would lie. Use a compensating action (a refund) and be explicit.
  • Commands hold live references. A serialized command cannot carry an open connection or a function. Store identifiers and rebuild.

What interviewers ask

  • "What is the Command pattern?" Encapsulating a request as an object, so it can be passed around, stored, queued and undone.
  • "How do you implement undo?" Each command has an undo(), and executed commands are kept on a stack.
  • "Command versus Strategy?" Strategy chooses how something is done. Command represents a specific request to do something, possibly later.
  • "Where do you see it in a web app?" Redux actions, job queues, editor commands.
  • "How does it help decoupling?" The invoker knows only execute(), never the receiver.

The mistake to avoid

A Command that does its own work instead of delegating to the receiver. If LightOnCommand contains the logic for turning a light on rather than calling light.turnOn(), the command is no longer a thin wrapper around a request, and the same behavior cannot be reused from anywhere else. The command should carry the request, the receiver should do the work.

The mechanism above, a request represented as an object with one method, is the same in JavaScript, Python or Java. Only the syntax changes.

Now practice it

1 challenge · 2 quizzes

Reading this page is the cheap half. These are the exercises that make you use it.