1. What Is It?
The pattern encapsulates a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.
Without it, action logic is either hard-coded in the invoker (tight between "who triggers" and "what executes") or scattered across ad hoc callbacks. You can't queue actions, log them for replay, or undo them without invasive changes. turns each request into a first-class object that can be stored, passed around, composed, and reversed.
A text editor currently handles 'Bold', 'Italic', and 'Undo' actions by calling formatting functions directly from button click handlers. The team now wants to add a macro-recording feature that replays a sequence of user actions. Why does the current design make this difficult, and what does the Command pattern offer to address it?
2. How It Works
Participants:
- — with
execute()and optionallyundo() - ConcreteCommand — binds a specific action to a specific Receiver; implements
execute()by calling receiver methods; stores enough to implementundo() - Receiver — the object that knows how to perform the actual work (e.g.,
TextEditor,Light,FileSystem) - Invoker — asks the to carry out the request; may queue commands, log them, or call
undo(); does not know what the command does or who the receiver is - Client — creates ConcreteCommand objects, wires them to Receivers, and passes them to the Invoker
Step-by-step:
- Define
withexecute()(andundo()if reversibility is needed) - Implement
ConcreteCommand— stores a reference to its Receiver and any parameters needed;execute()delegates to Receiver Invokerholds a command (or a queue); callsexecute()without knowing the details- Client wires:
command = ConcreteCommand(receiver, args); invoker.set_command(command)
Python:
from __future__ import annotations
from abc import ABC, abstractmethod
from collections import deque
class Command(ABC):
@abstractmethod
def execute(self) -> None:
pass
@abstractmethod
def undo(self) -> None:
pass
class TextEditor:
def __init__(self) -> None:
self._content: str = ""
def type(self, text: str) -> None:
self._content += text
def delete(self, character_count: int) -> str:
start = max(len(self._content) - character_count, 0)
deleted = self._content[start:]
self._content = self._content[:start]
return deleted
def get_content(self) -> str:
return self._content
class TypeTextCommand(Command):
def __init__(self, editor: TextEditor, text: str) -> None:
self._editor = editor
self._text = text
def execute(self) -> None:
self._editor.type(self._text)
def undo(self) -> None:
self._editor.delete(len(self._text))
class DeleteTextCommand(Command):
def __init__(self, editor: TextEditor, character_count: int) -> None:
self._editor = editor
self._character_count = character_count
self._deleted_text: str = ""
def execute(self) -> None:
self._deleted_text = self._editor.delete(self._character_count)
def undo(self) -> None:
self._editor.type(self._deleted_text)
class EditorHistory:
"""Invoker — executes commands and manages undo history."""
def __init__(self) -> None:
self._history: deque[Command] = deque()
def execute(self, command: Command) -> None:
command.execute()
self._history.append(command)
def undo(self) -> None:
if self._history:
command = self._history.pop()
command.undo()
# Usage
editor = TextEditor()
history = EditorHistory()
history.execute(TypeTextCommand(editor, "Hello, "))
history.execute(TypeTextCommand(editor, "World!"))
print(editor.get_content()) # Hello, World!
history.undo()
print(editor.get_content()) # Hello, (trailing space — "World!" was deleted)
history.execute(TypeTextCommand(editor, "Python!"))
print(editor.get_content()) # Hello, Python!Java:
import java.util.ArrayDeque;
import java.util.Deque;
// Command interface
public interface Command {
void execute();
void undo();
}
// Receiver
public class TextEditor {
private StringBuilder content = new StringBuilder();
public void type(String text) {
content.append(text);
}
public String delete(int characterCount) {
int start = content.length() - characterCount;
String deleted = content.substring(start);
content.delete(start, content.length());
return deleted;
}
public String getContent() {
return content.toString();
}
}
// ConcreteCommand A
public class TypeTextCommand implements Command {
private final TextEditor editor;
private final String text;
public TypeTextCommand(TextEditor editor, String text) {
this.editor = editor;
this.text = text;
}
@Override
public void execute() {
editor.type(text);
}
@Override
public void undo() {
editor.delete(text.length());
}
}
// ConcreteCommand B
public class DeleteTextCommand implements Command {
private final TextEditor editor;
private final int characterCount;
private String deletedText;
public DeleteTextCommand(TextEditor editor, int characterCount) {
this.editor = editor;
this.characterCount = characterCount;
}
@Override
public void execute() {
deletedText = editor.delete(characterCount);
}
@Override
public void undo() {
editor.type(deletedText);
}
}
// Invoker
public class EditorHistory {
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();
}
}
public static void main(String[] args) {
TextEditor editor = new TextEditor();
EditorHistory editorHistory = new EditorHistory();
editorHistory.execute(new TypeTextCommand(editor, "Hello, "));
editorHistory.execute(new TypeTextCommand(editor, "World!"));
System.out.println(editor.getContent()); // Hello, World!
editorHistory.undo();
System.out.println(editor.getContent()); // Hello, (trailing space — "World!" was deleted)
editorHistory.execute(new TypeTextCommand(editor, "Java!"));
System.out.println(editor.getContent()); // Hello, Java!
}
}Execute commands, then undo the latest one
EditorHistory executes and stores commands; undo pops the latest command and reverses its change to TextEditor.
The editor is the receiver, and its text starts empty.
Text version
- The editor is the receiver, and its text starts empty.
- The history is the invoker, with no saved commands yet.
- History runs the first typing command and saves it for undo.
- The second command appends World and an exclamation mark.
- Printing the receiver shows the text from both commands.
- Undo pops the last command, which removes the text it added.
- Printing again shows only the first command's text.
- A new command appends Python and an exclamation mark.
- The final print shows the receiver's updated text.
In the Command pattern, an EditorHistory class holds a stack of Command objects and calls execute() and undo() on them. Which role does EditorHistory fulfill, and what is a key characteristic of that role?
3. Variants & Comparisons
| Variant | How It Works | Pros | Cons | Best For |
|---|---|---|---|---|
| Basic Command | execute() only | Simple, no extra state | No undo | Fire-and-forget actions |
| Undoable Command | execute() + undo() with stored state | Ctrl+Z, history navigation | Every command must keep enough state to reverse itself | Editors, drawing apps, transactions |
| Macro Command | Composite of commands; execute() calls all children | Compose sequences, transactional batches | Undo must reverse in order | Batch operations, wizards |
| Queued Commands | Commands stored in a queue; worker thread drains queue | Async, rate-limiting, scheduling | Command must be serializable if persisted | Job queues, task schedulers |
Choose the command variant for the job
Basic commands execute, undoable commands reverse changes, macros group commands, and queued commands defer work.
Basic commands execute, undoable commands reverse changes, macros group commands, and queued commands defer work.
Text version
- Basic commands execute, undoable commands reverse changes, macros group commands, and queued commands defer work.
- Basic fits fire-and-forget actions, undoable fits editors, macro fits batches, and queued fits task schedulers.
- Basic has no undo, undoable commands must keep undo state, macros undo in reverse order, and persisted queued commands must be serializable.
A drawing application needs to support Ctrl+Z undo functionality, allowing users to reverse their last several actions. Which Command pattern variant is most appropriate, and what key requirement comes with it?
4. When to Use It (and When NOT To)
Use when:
- You need undo/redo (editors, drawing tools, IDEs)
- You need to queue, schedule, or log operations
- You want to parameterize objects with operations (toolbar buttons that are wired to different commands)
- You need transactional behavior (execute a batch; on failure, undo the whole batch)
Don't use when:
- The operation is simple, synchronous, and will never need undo — a direct method call is cleaner
- Commands have no to store and no need for queueing — adds overhead without benefit
Decision triggers:
- "Users need Ctrl+Z" → with
undo() - "I need to schedule these operations for later or retry them on failure" → Command queue
- "I want to log every user action for replay or audit" → Command as log entry
- "I need to support macro recording" → Command
Anti-patterns:
- Fat Commands: Command starts doing business logic that belongs in the Receiver. Command should be a thin dispatcher — business logic lives in the Receiver.
- Missing undo : Implementing
execute()but not saving undo state meansundo()can't work. Every undoable command must keep what it needs to reverse itself. It keeps either its parameters when an inverse operation exists (TypeTextCommand), or the state it changes, captured when it executes (DeleteTextCommand).
A developer is building a text editor and implements a Command pattern for each editing action. They write the execute() method carefully, but skip implementing any state-saving logic because they want to keep the Command classes small. What problem will this cause?
5. Real-World Usage
1. Java Runnable and Callable: Both are single-method interfaces that encapsulate a unit of work — exactly the pattern. ExecutorService.submit(runnable) is an Invoker that queues and executes commands. FutureTask wraps a Callable — deferred execution is a queue of one.
2. UI toolkits (Swing Action, JavaFX): javax.swing.Action is a Command . Menu items, toolbar buttons, and keyboard shortcuts all share the same Action instance — the Invoker wires the trigger, the Command defines the behavior. This decouples "button clicked" from "what happens."
3. Database transaction logs / Write-Ahead Logs (WAL): Each database mutation is recorded as a log record in the WAL before being applied. On crash recovery, the log is replayed (execute) or rolled back (undo). ACID undo/redo logging uses the same idea as the Command pattern, but WAL records describe low-level data changes, not command objects.
A relational database crashes mid-transaction after several rows have been modified. When the server restarts, it replays its Write-Ahead Log (WAL) to restore consistency. Which role does each recorded WAL entry play in the Command pattern?
6. Interview Cheat Sheet
Key sentences:
- " encapsulates a request as an object — this lets you queue, log, and undo operations without the invoker knowing anything about what's being executed."
- "The critical implementation detail for undo is that each must keep the it needs to reverse. That is its parameters if an inverse operation exists, otherwise a snapshot taken before it mutates."
- "The Invoker is decoupled from the Receiver entirely — it only knows about the Command . This is what lets you wire the same Command to a button, a menu item, and a keyboard shortcut."
- "Macro Commands ( + Command) let you group a sequence of actions into an atomic, undoable unit — useful for transactions and batch operations."
Common follow-up questions:
Q: How do you implement redo in addition to undo?
A: Maintain two stacks — an undo stack and a redo stack. execute() pushes to undo, clears redo. undo() pops from undo, pushes to redo. redo() pops from redo, pushes back to undo.
Q: How is Command different from ? A: encapsulates an algorithm that the Context uses ongoing. Command encapsulates a one-time request that may be queued, logged, or reversed. Strategy is about how something is done; Command is about what was requested and when.
Q: How would you persist commands for replay?
A: Serialize the Command object (with all parameters) to a log (database, file, Kafka topic). On replay, deserialize and call execute() in order. This is command sourcing. Event sourcing differs: it stores the events that commands produced and rebuilds by applying them, not by re-executing commands.
Connections:
- Command + → Macro Command (transactional batch)
- Command + → Commands as events; observers react to commands
- Command is the OOP formalization of CQRS command objects. In event sourcing, handling a command produces the events that get stored
- Command + Memento → store snapshots alongside commands for richer undo