Chapter 05 · Article 24 of 55
Command Pattern
Intent: Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.
Article outline15 sections on this page+
Overview
Intent: Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.
The Command pattern turns a request into a stand-alone object that contains all information about the request - the method to call, the method's arguments, and the object that owns the method. This transformation lets you pass requests as method arguments, delay or queue a request's execution, and support undoable operations.
Also known as: Action, Transaction
Type: Behavioural Design Pattern
Complexity: **
Problem It Solves
In many systems, the object that invokes an operation is tightly coupled to the object that knows how to perform it. This coupling creates several problems:
-
Invoker-Receiver Coupling: A GUI button directly calls business logic, making it impossible to reuse the button for different operations without modifying its code.
-
No Undo/Redo Support: Without a history of operations, reversing actions requires ad-hoc state snapshots or manual bookkeeping.
-
No Command Queuing: Operations that need to be scheduled, batched, or executed remotely cannot be easily serialized or deferred.
-
No Macro Commands: Composing multiple operations into a single logical unit (e.g., "format document" = bold title + indent paragraphs + add footer) requires complex orchestration code.
-
No Logging/Auditing: Without reified operations, you cannot replay, audit, or persist the sequence of actions performed on a system.
The Command pattern solves all of these by introducing an abstraction layer - the command object - between the invoker and the receiver.
When to Use / When NOT to Use
| When to Use | When NOT to Use |
|---|---|
| You need to parameterize objects with operations | Simple direct method calls with no need for decoupling |
| You need undo/redo functionality | Operations that are trivially reversible via state reset |
| You need to queue, schedule, or log operations | Overhead of command objects outweighs benefits (simple CRUD) |
| You need to support macro (composite) commands | System has only one invoker calling one receiver |
| You need to implement transactional behaviour | Real-time systems where object creation latency matters |
| You want to decouple UI from business logic | When callbacks or lambdas suffice and no history is needed |
| You need remote execution or serialization of requests | Stateless request-response with no side effects to track |
Key Concepts & Theory
The Four Roles
| Role | Responsibility |
|---|---|
| Command | Declares the interface (execute, undo) for performing an operation |
| ConcreteCommand | Implements execute by invoking operations on the Receiver; stores state for undo |
| Receiver | The object that actually performs the work (business logic) |
| Invoker | Asks the command to carry out the request; holds command references |
| Client | Creates ConcreteCommand objects and sets their receivers |
Command as a First-Class Object
Once a request is encapsulated as an object, it gains all the properties of objects:
- It can be stored in data structures (stacks, queues, lists)
- It can be serialized to disk or transmitted over a network
- It can be parameterized - the same invoker works with any command
- It can be composed - multiple commands form a macro command
The Undo Stack
The invoker maintains a history stack of executed commands. Each command stores enough state to reverse its effect:
Execute: push command onto history stack
Undo: pop from history stack, call command.undo()
Redo: pop from redo stack, call command.execute(), push onto history
Key invariant: After undo, if a new command is executed, the redo stack is cleared (the "branching" timeline is discarded).
Macro Commands (Composite Commands)
A MacroCommand holds a list of sub-commands and executes them sequentially. Undo reverses them in reverse order. This is the Command pattern combined with the Composite pattern.
ASCII Class Diagram
┌─────────────────────────────────────────────────────────────────────┐
│ CLIENT │
│ (creates commands, assigns receivers, configures invoker) │
└──────────────────────────────┬──────────────────────────────────────┘
│ creates
▼
┌──────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ INVOKER │ │ <<interface>> │ │ RECEIVER │
│ │ │ Command │ │ │
│ - history[] │ │──────────────────│ │ + action() │
│ │ holds │ + execute() │ │ + undoAction() │
│ + execute- │──────▶│ + undo() │ │ │
│ Command() │ └──────────────────┘ └────────▲─────────┘
│ + undo() │ △ │
│ + redo() │ │ implements │ calls
└──────────────┘ │ │
┌──────────┴──────────┐ │
│ │ │
┌─────────────────┐ ┌─────────────────┐ │
│ ConcreteCommandA│ │ ConcreteCommandB│ │
│─────────────────│ │─────────────────│ │
│ - receiver │───┘ - receiver │───────┘
│ - prevState │ - prevState │
│─────────────────│ │─────────────────│
│ + execute() │ │ + execute() │
│ + undo() │ │ + undo() │
└─────────────────┘ └─────────────────┘
Pseudocode Implementation
Example 1: Text Editor with Undo/Redo
// Command Interface
interface Command {
execute()
undo()
}
// Receiver
class TextEditor {
content = ""
selectionStart = 0
selectionEnd = 0
insert(text, position):
content = content[0..position] + text + content[position..]
delete(start, end):
removed = content[start..end]
content = content[0..start] + content[end..]
return removed
applyBold(start, end):
// wraps selection in **bold** markers
insert("**", end)
insert("**", start)
removeBold(start, end):
// removes bold markers around selection
delete(end, end+2)
delete(start, start+2)
}
// Concrete Commands
class TypeCommand implements Command {
editor: TextEditor
text: String
position: Int
constructor(editor, text, position):
this.editor = editor
this.text = text
this.position = position
execute():
editor.insert(text, position)
undo():
editor.delete(position, position + text.length)
}
class DeleteCommand implements Command {
editor: TextEditor
start: Int
end: Int
deletedText: String // saved for undo
constructor(editor, start, end):
this.editor = editor
this.start = start
this.end = end
execute():
deletedText = editor.delete(start, end)
undo():
editor.insert(deletedText, start)
}
class BoldCommand implements Command {
editor: TextEditor
start: Int
end: Int
constructor(editor, start, end):
this.editor = editor
this.start = start
this.end = end
execute():
editor.applyBold(start, end)
undo():
editor.removeBold(start, end)
}
// Invoker
class EditorInvoker {
historyStack: Stack<Command> = []
redoStack: Stack<Command> = []
executeCommand(cmd: Command):
cmd.execute()
historyStack.push(cmd)
redoStack.clear() // new action invalidates redo
undo():
if historyStack.isEmpty(): return
cmd = historyStack.pop()
cmd.undo()
redoStack.push(cmd)
redo():
if redoStack.isEmpty(): return
cmd = redoStack.pop()
cmd.execute()
historyStack.push(cmd)
}
// Client Usage
editor = new TextEditor()
invoker = new EditorInvoker()
invoker.executeCommand(new TypeCommand(editor, "Hello World", 0))
// content: "Hello World"
invoker.executeCommand(new BoldCommand(editor, 0, 5))
// content: "**Hello** World"
invoker.undo()
// content: "Hello World"
invoker.redo()
// content: "**Hello** World"
Example 2: Smart Home Remote Control
// Receivers
class Light {
isOn = false
turnOn(): isOn = true
turnOff(): isOn = false
}
class CeilingFan {
speed = 0 // 0=off, 1=low, 2=medium, 3=high
setSpeed(s): speed = s
getSpeed(): return speed
}
// Concrete Commands
class LightOnCommand implements Command {
light: Light
execute(): light.turnOn()
undo(): light.turnOff()
}
class LightOffCommand implements Command {
light: Light
execute(): light.turnOff()
undo(): light.turnOn()
}
class FanSpeedCommand implements Command {
fan: CeilingFan
newSpeed: Int
prevSpeed: Int
constructor(fan, newSpeed):
this.fan = fan
this.newSpeed = newSpeed
execute():
prevSpeed = fan.getSpeed()
fan.setSpeed(newSpeed)
undo():
fan.setSpeed(prevSpeed)
}
// Macro Command
class MacroCommand implements Command {
commands: List<Command>
constructor(commands):
this.commands = commands
execute():
for cmd in commands: cmd.execute()
undo():
for cmd in commands.reversed(): cmd.undo()
}
// Invoker
class RemoteControl {
slots: Map<Int, Command>
history: Stack<Command> = []
setCommand(slot, cmd):
slots[slot] = cmd
pressButton(slot):
cmd = slots[slot]
cmd.execute()
history.push(cmd)
pressUndo():
if history.isEmpty(): return
cmd = history.pop()
cmd.undo()
}
// Client
light = new Light()
fan = new CeilingFan()
remote = new RemoteControl()
remote.setCommand(1, new LightOnCommand(light))
remote.setCommand(2, new FanSpeedCommand(fan, 3))
remote.setCommand(3, new MacroCommand([
new LightOnCommand(light),
new FanSpeedCommand(fan, 2)
]))
remote.pressButton(1) // light on
remote.pressButton(2) // fan high
remote.pressUndo() // fan back to 0
Command Queue and Scheduling
Commands, being self-contained objects, are naturally suited for queuing, logging, and replay.
class CommandQueue {
queue: Queue<Command> = []
log: List<CommandLogEntry> = []
enqueue(cmd: Command):
queue.add(cmd)
processNext():
if queue.isEmpty(): return
cmd = queue.dequeue()
cmd.execute()
log.append(CommandLogEntry(cmd, timestamp=now()))
processAll():
while not queue.isEmpty():
processNext()
// Replay from log (e.g., after crash recovery)
replay(fromTimestamp):
for entry in log:
if entry.timestamp >= fromTimestamp:
entry.command.execute()
// Scheduled execution
schedule(cmd: Command, executeAt: Timestamp):
scheduler.addJob(executeAt, () -> {
cmd.execute()
log.append(CommandLogEntry(cmd, timestamp=executeAt))
})
}
// Serialization for persistence / network transfer
class SerializableCommand {
serialize(cmd: Command) -> JSON:
return { type: cmd.className, params: cmd.getParams() }
deserialize(json: JSON) -> Command:
return CommandFactory.create(json.type, json.params)
}
Use cases for command queuing:
- Job queues: Background workers pull commands from a queue and execute them
- Crash recovery: Replay logged commands from the last checkpoint
- Distributed systems: Serialize commands and send over the network
- Rate limiting: Buffer commands and process at controlled intervals
- Batch processing: Accumulate commands and execute in a single transaction
Real-World Examples
| Domain | Example | Command | Invoker | Receiver |
|---|---|---|---|---|
| GUI Applications | Menu items, toolbar buttons, keyboard shortcuts | MenuItem action | Menu/Button | Application logic |
| Transaction Systems | Banking transfers, order processing | TransferFundsCommand | TransactionManager | AccountService |
| Task Schedulers | Cron jobs, delayed execution | ScheduledTask | Scheduler | Worker service |
| Database Migrations | Schema changes with rollback | MigrationStep (up/down) | MigrationRunner | Database |
| Game Input Replay | Recording and replaying player actions | InputCommand (move, shoot) | ReplayEngine | GameState |
| Text Editors | Undo/redo in VS Code, Vim | EditAction | UndoManager | Document buffer |
| CI/CD Pipelines | Build steps, deploy steps | PipelineStep | PipelineRunner | Build environment |
| Network Requests | Retry-able HTTP operations | RequestCommand | RetryHandler | HTTPClient |
Command vs Strategy vs Observer
| Aspect | Command | Strategy | Observer |
|---|---|---|---|
| Intent | Encapsulate a request as an object | Define a family of interchangeable algorithms | Notify dependents of state changes |
| Relationship | Invoker → Command → Receiver | Context → Strategy | Subject → Observer |
| Decouples | "What to do" from "when to do it" | "How to do it" from "who uses it" | "State change" from "reaction to change" |
| Stores state | Yes (for undo) | No (stateless algorithms) | No (observers maintain own state) |
| Composable | Yes (macro commands) | No (one strategy at a time) | Yes (multiple observers) |
| Typical use | Undo, queuing, logging | Algorithm selection at runtime | Event systems, pub/sub |
| Lifetime | Created, executed, possibly stored | Set once, swapped occasionally | Registered, notified, unregistered |
| Direction | One-to-one (invoker to receiver) | One-to-one (context to algorithm) | One-to-many (subject to observers) |
Key distinction: A Command says "do this specific thing (and remember how to undo it)". A Strategy says "do this kind of thing, but I don't care how". An Observer says "something happened, react however you want".
Advantages & Disadvantages
| Advantages | Disadvantages |
|---|---|
| Decouples invoker from receiver (SRP) | Increases number of classes in the system |
| Easy to add new commands (OCP) | Can be overkill for simple operations |
| Supports undo/redo naturally | Memory overhead from storing command history |
| Enables command queuing and scheduling | Complexity in managing undo for complex state |
| Commands are composable (macro commands) | Serialization of commands with complex state is non-trivial |
| Supports logging, auditing, and replay | Debugging can be harder (indirection) |
| Enables transactional behaviour | Potential for stale receiver references |
| Facilitates testing (commands are testable units) | Undo correctness is hard to guarantee in concurrent systems |
Constraints & Edge Cases
Command Serialization
When commands need to be persisted or sent over a network, they must be serializable. This is challenging when commands hold references to complex objects (receivers). Solutions:
- Store receiver identifiers instead of references; resolve at execution time
- Use the Memento pattern alongside Command for state snapshots
- Implement a CommandFactory that reconstructs commands from serialized data
Large Undo History Memory
An unbounded history stack can consume significant memory, especially for commands that store large state snapshots (e.g., image editor operations). Mitigations:
- Limit history depth - discard oldest commands beyond N entries
- Checkpoint strategy - periodically save full state, only keep commands since last checkpoint
- Delta storage - store only diffs rather than full state copies
- Compress - serialize and compress old command state
Compensating Commands (When Undo Is Impossible)
Some operations cannot be truly reversed (e.g., sending an email, charging a credit card, deleting data from an external system). In these cases:
- Use compensating commands - a new forward action that logically reverses the effect (e.g., refund instead of "un-charge")
- Implement saga pattern - a sequence of commands with corresponding compensations
- Mark commands as non-undoable and prevent undo past that point
Idempotency
Commands that may be retried (network failures, queue redelivery) must be idempotent:
- Assign unique IDs to commands; check for duplicate execution
- Design execute() to produce the same result regardless of how many times it runs
- Use conditional writes (e.g., "set X to 5" is idempotent; "increment X" is not)
Concurrency Concerns
- Commands modifying shared state need synchronization
- Undo in multi-user systems requires conflict resolution (operational transformation or CRDTs)
- Command queues need thread-safe implementations
Interview Follow-ups
Q1: How would you implement undo in a collaborative text editor where multiple users edit simultaneously?
A: In collaborative editors, simple undo stacks break because undoing your action may conflict with others' edits that happened after. The solution is Operational Transformation (OT) or CRDTs. Each command is transformed against concurrent operations before being applied or undone. The undo doesn't simply reverse the original command - it computes a new command that reverses the effect of the original in the current document state. Google Docs uses OT; Figma uses CRDTs.
Q2: How does the Command pattern relate to Event Sourcing?
A: Event Sourcing is essentially the Command pattern applied at the architectural level. Instead of storing current state, you store the sequence of commands (events) that produced that state. Current state is derived by replaying all events. This gives you: complete audit trail, ability to rebuild state at any point in time, natural undo (replay up to N-1), and temporal queries. The key difference: in Event Sourcing, events are facts that happened (past tense, immutable), while commands are requests to do something (may be rejected).
Q3: How would you handle a command that partially executes before failing?
A: This is the transaction problem. Options: (1) All-or-nothing - validate preconditions before execute, use transactions to ensure atomicity. (2) Compensating actions - if step 3 of 5 fails, run compensations for steps 1 and 2 in reverse order (saga pattern). (3) Two-phase commit - prepare all sub-operations first, then commit all or abort all. (4) Checkpoint and rollback - save state before execution, restore on failure. The choice depends on whether operations are local (use transactions) or distributed (use sagas).
Hint-Only Questions
H1: Design a command-based plugin system where third-party developers can register custom commands.
Hints:
- Think about a CommandRegistry with register(name, factory) and execute(name, params)
- Consider sandboxing - how do you prevent malicious commands from accessing unauthorized resources?
- Think about command validation, versioning, and capability-based permissions
H2: How would you implement a "smart" undo that groups related commands (e.g., typing "hello" is one undo step, not five)?
Hints:
- Consider time-based grouping - commands within 500ms of each other form a batch
- Think about command coalescing - consecutive TypeCommands merge into one
- Look at how the Memento pattern could snapshot state at "pause points" rather than every keystroke
Counter Questions to Ask Interviewer
-
"What's the expected volume of commands?" - Determines whether you need a bounded history, persistent queue, or in-memory stack.
-
"Do commands need to survive process restarts?" - Drives decisions about serialization, event sourcing, and persistence strategy.
-
"Is undo required? If so, for how many levels?" - Unbounded undo has very different memory and complexity implications than "undo last 10 actions."
-
"Are there commands that cannot be undone?" - Determines whether you need compensating commands, saga pattern, or confirmation dialogs.
-
"Is this a single-user or multi-user system?" - Multi-user undo requires conflict resolution (OT/CRDTs), which fundamentally changes the design.
-
"Do commands need to be composable into macros?" - Determines whether you need the Composite pattern integration.
-
"What are the failure modes?" - Partial execution, network failures, and timeouts each require different strategies (sagas, retries, idempotency keys).
References & Whitepapers
-
Gang of Four (GoF) - Design Patterns: Elements of Reusable Object-Oriented Software (1994), Chapter 5: Behavioral Patterns - Command.
-
CQRS (Command Query Responsibility Segregation) - Greg Young (2010). Separates read and write models; the "C" in CQRS is directly the Command pattern applied at the architectural level. Commands mutate state; queries read it.
-
Event Sourcing - Martin Fowler. Stores state as a sequence of events (commands that were executed). Naturally supports replay, audit, and temporal queries. Closely related to command logging.
-
Saga Pattern - Hector Garcia-Molina & Kenneth Salem, "Sagas" (1987). Long-lived transactions as sequences of commands with compensating actions - directly builds on the Command pattern.
-
Head First Design Patterns - Freeman & Robson, Chapter 6: The Command Pattern - excellent practical examples with remote control analogy.
-
Martin Fowler - "Command" pattern entry in Patterns of Enterprise Application Architecture (PoEAA).
-
Udi Dahan - "Clarified CQRS" - explains the relationship between commands, events, and domain models in distributed systems.