Command
Command is a behavioral pattern that wraps a request - the receiver, the action, and its arguments - into a self-contained object, so the request itself can be passed around, stored, and invoked independently of whatever triggered it.
Problem
Consider a drawing app where the same "delete shape" action can be triggered from a toolbar button, a keyboard shortcut, and a right-click menu - and where the app also needs to support undo.
If each UI element calls the deletion logic directly, that logic ends up duplicated in three places, and none of them has any record of what just happened, so undo has nowhere to hook in. Wiring undo support after the fact usually means threading extra state through every place that can trigger an action.
The real issue is that "what to do" and "how it got triggered" are tangled together, with no single object representing the action itself that could be stored, replayed, or reversed.
Solution
Command pulls the request itself out into its own object: everything needed to perform the action - which receiver to call, which operation, with which arguments - is captured behind a single `execute()` method.
The UI element that triggers the action no longer calls the receiver directly; it just holds a command object and calls `execute()` on it, without caring what that command actually does or which object ends up doing the work. Because the command is a real object rather than a bare function call, it can be stored in a history list, serialized to disk, handed to a queue for later execution, or paired with an `undo()` method that reverses whatever `execute()` did.
When to Use
- Reach for Command when you want to hand an operation to something else - a button, a menu item, a scheduler - as a value, rather than hardcoding the call site.
- It's a natural fit when requests need to be queued, delayed, retried, or dispatched to a worker rather than executed immediately in place.
- Use it whenever the application must support reversing actions the user has taken, since each command can carry its own undo logic.
Real-World Examples
- **`document.execCommand` and editor undo stacks** - rich-text editors represent formatting and edit operations as discrete command objects pushed onto a history stack for multi-level undo.
- **GUI toolkit actions** - frameworks like Qt and Swing model menu items, toolbar buttons, and shortcuts as `QAction`/`Action` objects so the same command can be triggered from multiple UI entry points.
- **Background job queues** - systems like Sidekiq, Celery, and Bull serialize a job's target method and arguments into a command-like payload that a worker process picks up and executes later.
Structure
The Invoker holds a command and triggers it without knowing what it does. The Command interface exposes a uniform way to run (and optionally undo) an action. ConcreteCommand instances bind together a specific Receiver and the operation to call on it. The Receiver is where the actual work happens; the Client wires receivers, commands, and invokers together.
Participants
Holds a reference to a command and triggers its execution - right away, on a delay, or as part of a queue - without knowing what the command does internally.
In the diagram: Invoker.
A minimal interface, usually just `execute()` and optionally `undo()`, that every concrete command must provide so invokers can treat them uniformly.
In the diagram: Command.
Stores a reference to its Receiver plus whatever arguments the operation needs, and forwards the actual work to the receiver when executed.
Owns the domain logic that gets the work done. It has no idea it's being driven by a command - any object with a callable method can play this role.
In the diagram: Receiver.
Assembles the pieces: creates a Receiver, wraps the desired operation on it in a ConcreteCommand, and hands the command to an Invoker.
In the diagram: Client.
class TextEditor {
#content = '';
insert(text, position) {
this.#content =
this.#content.slice(0, position) +
text +
this.#content.slice(position);
}
delete(position, length) {
this.#content =
this.#content.slice(0, position) +
this.#content.slice(position + length);
}
getContent() { return this.#content; }
}
class InsertCommand {
constructor(editor, text, position) {
this.editor = editor;
this.text = text;
this.position = position;
}
execute() {
this.editor.insert(this.text, this.position);
}
undo() {
this.editor.delete(this.position, this.text.length);
}
}
class CommandHistory {
#history = [];
execute(command) {
command.execute();
this.#history.push(command);
}
undo() {
this.#history.pop()?.undo();
}
}
const editor = new TextEditor();
const history = new CommandHistory();
history.execute(new InsertCommand(editor, 'Hello', 0));
history.execute(new InsertCommand(editor, ' World', 5));
console.log(editor.getContent());
history.undo();
console.log(editor.getContent());
Step 1 of 5
TextEditor is the receiver - it does the actual work
`insert` and `delete` mutate the private `#content` string directly - the editor knows nothing about commands or undo, it just performs edits when asked.
Step 2 of 5
InsertCommand bundles a request and knows how to reverse it
The constructor captures the `editor`, `text` and `position` it needs; `execute()` calls `editor.insert(...)`, and `undo()` calls the inverse operation `editor.delete(...)` with the same position and text length.
Step 3 of 5
CommandHistory is the invoker that keeps a stack of executed commands
`execute(command)` calls `command.execute()` and then pushes it onto `#history`; `undo()` pops the last command off that stack and calls its `undo()` - the invoker never needs to know what kind of command it's running.
Step 4 of 5
The client builds commands and hands them to the history
Two `InsertCommand` objects are created and passed to `history.execute(...)` - the client decides what request to make, while the history decides when it actually runs.
Step 5 of 5
undo() reverses the last command without the editor being involved
`history.undo()` pops the second `InsertCommand` and calls its own `undo()`, which deletes exactly what it inserted - the editor's content reverts even though the editor exposes no undo logic itself.
class TextEditor {
private content = '';
insert(text: string, position: number): void {
this.content =
this.content.slice(0, position) +
text +
this.content.slice(position);
}
delete(position: number, length: number): void {
this.content =
this.content.slice(0, position) +
this.content.slice(position + length);
}
getContent(): string { return this.content; }
}
interface Command {
execute(): void;
undo(): void;
}
class InsertCommand implements Command {
constructor(
private readonly editor: TextEditor,
private readonly text: string,
private readonly position: number
) {}
execute(): void {
this.editor.insert(this.text, this.position);
}
undo(): void {
this.editor.delete(this.position, this.text.length);
}
}
class CommandHistory {
private history: Command[] = [];
execute(command: Command): void {
command.execute();
this.history.push(command);
}
undo(): void {
this.history.pop()?.undo();
}
}
const editor = new TextEditor();
const history = new CommandHistory();
history.execute(new InsertCommand(editor, 'Hello', 0));
history.execute(new InsertCommand(editor, ' World', 5));
console.log(editor.getContent());
history.undo();
console.log(editor.getContent());
import java.util.ArrayDeque;
import java.util.Deque;
class TextEditor {
private StringBuilder content = new StringBuilder();
public void insert(String text, int position) { content.insert(position, text); }
public void delete(int position, int length) { content.delete(position, position + length); }
public String getContent() { return content.toString(); }
}
interface Command {
void execute();
void undo();
}
class InsertCommand implements Command {
private final TextEditor editor;
private final String text;
private final int position;
InsertCommand(TextEditor editor, String text, int position) {
this.editor = editor; this.text = text; this.position = position;
}
public void execute() { editor.insert(text, position); }
public void undo() { editor.delete(position, text.length()); }
}
class CommandHistory {
private final Deque<Command> history = new ArrayDeque<>();
public void execute(Command command) { command.execute(); history.push(command); }
public void undo() { if (!history.isEmpty()) history.pop().undo(); }
}
TextEditor editor = new TextEditor();
CommandHistory hist = new CommandHistory();
hist.execute(new InsertCommand(editor, "Hello", 0));
hist.execute(new InsertCommand(editor, " World", 5));
System.out.println(editor.getContent());
hist.undo();
System.out.println(editor.getContent());
class TextEditor
{
private string _content = "";
public void Insert(string text, int position) =>
_content = _content[..position] + text + _content[position..];
public void Delete(int position, int length) =>
_content = _content[..position] + _content[(position + length)..];
public string GetContent() => _content;
}
interface ICommand { void Execute(); void Undo(); }
class InsertCommand : ICommand
{
private readonly TextEditor _editor;
private readonly string _text;
private readonly int _position;
public InsertCommand(TextEditor editor, string text, int position)
{ _editor = editor; _text = text; _position = position; }
public void Execute() => _editor.Insert(_text, _position);
public void Undo() => _editor.Delete(_position, _text.Length);
}
class CommandHistory
{
private readonly Stack<ICommand> _history = new();
public void Execute(ICommand cmd) { cmd.Execute(); _history.Push(cmd); }
public void Undo() { if (_history.Count > 0) _history.Pop().Undo(); }
}
var editor = new TextEditor();
var history = new CommandHistory();
history.Execute(new InsertCommand(editor, "Hello", 0));
history.Execute(new InsertCommand(editor, " World", 5));
Console.WriteLine(editor.GetContent());
history.Undo();
Console.WriteLine(editor.GetContent());
from __future__ import annotations
from abc import ABC, abstractmethod
class TextEditor:
def __init__(self) -> None:
self._content = ''
def insert(self, text: str, position: int) -> None:
self._content = (
self._content[:position] + text + self._content[position:]
)
def delete(self, position: int, length: int) -> None:
self._content = (
self._content[:position] + self._content[position + length:]
)
def get_content(self) -> str:
return self._content
class Command(ABC):
@abstractmethod
def execute(self) -> None: ...
@abstractmethod
def undo(self) -> None: ...
class InsertCommand(Command):
def __init__(self, editor: TextEditor, text: str, position: int) -> None:
self._editor = editor
self._text = text
self._position = position
def execute(self) -> None:
self._editor.insert(self._text, self._position)
def undo(self) -> None:
self._editor.delete(self._position, len(self._text))
class CommandHistory:
def __init__(self) -> None:
self._history: list[Command] = []
def execute(self, command: Command) -> None:
command.execute()
self._history.append(command)
def undo(self) -> None:
if self._history:
self._history.pop().undo()
editor = TextEditor()
history = CommandHistory()
history.execute(InsertCommand(editor, 'Hello', 0))
history.execute(InsertCommand(editor, ' World', 5))
print(editor.get_content())
history.undo()
print(editor.get_content())
Step 1 of 5
TextEditor is the receiver - it does the actual work
`insert` and `delete` mutate `self._content` directly - the editor knows nothing about commands or undo, it just performs edits when asked.
Step 2 of 5
Command is the abstract interface every command implements
`execute()` and `undo()` are both `@abstractmethod` - the history only ever depends on this shared interface, never on a concrete command class.
Step 3 of 5
InsertCommand bundles a request and knows how to reverse it
`__init__` captures `_editor`, `_text` and `_position`; `execute()` calls `self._editor.insert(...)`, and `undo()` calls the inverse `self._editor.delete(...)` using the same position and `len(self._text)`.
Step 4 of 5
CommandHistory is the invoker that keeps a stack of executed commands
`execute(command)` calls `command.execute()` then appends it to `self._history`; `undo()` pops the last entry and calls its `undo()` - the invoker only ever calls methods from the `Command` interface.
Step 5 of 5
The client creates commands; undo reverses them without touching the editor directly
Two `InsertCommand` objects go through `history.execute(...)`, then `history.undo()` pops the second one and calls its `undo()`, which deletes exactly what it inserted.
Click "Run" to execute this code in a sandboxed frame and see console output here.
TypeScript runs as plain JavaScript here - type annotations are stripped, not type-checked.
Click "Run" to execute this code in a sandboxed frame and see console output here.
Advantages
- UI or trigger code no longer needs to know how a request is fulfilled - it just holds a command object, which keeps the two sides free to change independently.
- Adding a brand-new action means writing one new command class; nothing about the invoker or the existing commands has to change.
- Because a command is a real object, it can be stacked in a history list to give you undo/redo almost for free.
- Commands can be serialized, queued, retried, or shipped off to run on a different thread or machine, since they carry everything needed to execute later.
Disadvantages
- There's an extra indirection layer between whoever triggers an action and whoever performs it, which adds a class or two even for trivial operations.
- A large application can accumulate a long tail of tiny, single-purpose command classes that are individually simple but collectively noisy.
- Supporting undo properly often requires each command to capture enough prior state to reverse itself, which is extra bookkeeping beyond just running the action once.
Question 1 of 10
What distinguishes the Receiver from the ConcreteCommand in this pattern?
Correct! The Receiver owns the domain logic that gets the work done and has no idea it's being driven by a command, while the ConcreteCommand stores a reference to its Receiver and forwards the actual work to it.
Not quite. The Receiver owns the domain logic that gets the work done and has no idea it's being driven by a command, while the ConcreteCommand stores a reference to its Receiver and forwards the actual work to it.
Question 2 of 10
How does CommandHistory implement undo without knowing what kind of command it's running?
Correct! CommandHistory keeps a stack of executed commands; undo() pops the last command off that stack and calls its own undo() - the invoker never needs to know what kind of command it's running.
Not quite. CommandHistory keeps a stack of executed commands; undo() pops the last command off that stack and calls its own undo() - the invoker never needs to know what kind of command it's running.
Question 3 of 10
Which of these is listed as a genuine benefit of Command?
Correct! Because a command is a real object rather than a bare function call, it can be stacked in a history list to give you undo/redo almost for free.
Not quite. Because a command is a real object rather than a bare function call, it can be stacked in a history list to give you undo/redo almost for free.
Question 4 of 10
What does Command wrap into a self-contained object?
Correct! Command wraps a request - the receiver, the action, and its arguments - into a self-contained object that can be passed around, stored, and invoked independently of whatever triggered it.
Not quite. Command wraps a request - the receiver, the action, and its arguments - into a self-contained object that can be passed around, stored, and invoked independently of whatever triggered it.
Question 5 of 10
In the structure, what is the Invoker responsible for?
Correct! The Invoker holds a reference to a command and triggers its execution - right away, on a delay, or as part of a queue - without knowing what the command does internally.
Not quite. The Invoker holds a reference to a command and triggers its execution - right away, on a delay, or as part of a queue - without knowing what the command does internally.
Question 6 of 10
In the TextEditor implementation, how does InsertCommand's undo() reverse an insertion?
Correct! InsertCommand's undo() calls the inverse operation editor.delete(...) with the same position and the inserted text's length, exactly reversing what execute() did.
Not quite. InsertCommand's undo() calls the inverse operation editor.delete(...) with the same position and the inserted text's length, exactly reversing what execute() did.
Question 7 of 10
What goes wrong when a toolbar button, keyboard shortcut, and menu item each call the delete-shape logic directly?
Correct! If each UI element calls the deletion logic directly, that logic ends up duplicated in three places, and none of them has any record of what just happened, so undo has nowhere to hook in.
Not quite. If each UI element calls the deletion logic directly, that logic ends up duplicated in three places, and none of them has any record of what just happened, so undo has nowhere to hook in.
Question 8 of 10
Once Command is applied, what does the UI element that triggers an action actually do?
Correct! The UI element that triggers the action no longer calls the receiver directly; it just holds a command object and calls execute() on it, without caring what that command actually does.
Not quite. The UI element that triggers the action no longer calls the receiver directly; it just holds a command object and calls execute() on it, without caring what that command actually does.
Question 9 of 10
What is a stated drawback of properly supporting undo with Command?
Correct! Supporting undo properly often requires each command to capture enough prior state to reverse itself, which is extra bookkeeping beyond just running the action once.
Not quite. Supporting undo properly often requires each command to capture enough prior state to reverse itself, which is extra bookkeeping beyond just running the action once.
Question 10 of 10
What tradeoff does Command introduce even for a trivial, one-off operation?
Correct! There's an extra indirection layer between whoever triggers an action and whoever performs it, which adds a class or two even for trivial operations.
Not quite. There's an extra indirection layer between whoever triggers an action and whoever performs it, which adds a class or two even for trivial operations.