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
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
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.