The Command pattern turns a request into a standalone object. This object contains everything about the request — what to do, who does it, and any parameters needed. Once it’s an object, we can pass it around, queue it, log it, and even undo it.
Think of it like a restaurant order slip. We tell the waiter “I want a burger.” The waiter writes it on a slip (the command object) and hands it to the kitchen. The kitchen executes it later. The waiter doesn’t cook. The kitchen doesn’t take orders. And if we change our mind, we can cancel the slip.
The Problem
Imagine we’re building a text editor. We need undo/redo, keyboard shortcuts, menu actions, and toolbar buttons — all triggering the same operations. Without Command:
- The UI directly calls business logic (tight coupling)
- Undo/redo is nearly impossible without tracking every change
- We can’t queue operations or replay them
- Adding a new action means changing multiple places
Key Components
Button, menu item,
keyboard shortcut.
undo()
Wraps the request
as an object.
TextDocument,
Light, Thermostat.
- Command — interface with
execute()and optionallyundo() - Concrete Command — implements the command, holds a reference to the receiver
- Invoker — triggers the command (doesn’t know what it does)
- Receiver — the object that actually performs the work
Implementation — Text Editor with Undo/Redo
from abc import ABC, abstractmethod
class Command(ABC):
@abstractmethod
def execute(self) -> None:
pass
@abstractmethod
def undo(self) -> None:
pass
class TextDocument:
"""The Receiver -- does the actual work."""
def __init__(self):
self.content = ""
def insert(self, text: str, position: int):
self.content = self.content[:position] + text + self.content[position:]
def delete(self, position: int, length: int) -> str:
deleted = self.content[position:position + length]
self.content = self.content[:position] + self.content[position + length:]
return deleted
class InsertCommand(Command):
def __init__(self, document: TextDocument, text: str, position: int):
self.document = document
self.text = text
self.position = position
def execute(self):
self.document.insert(self.text, self.position)
def undo(self):
self.document.delete(self.position, len(self.text))
class DeleteCommand(Command):
def __init__(self, document: TextDocument, position: int, length: int):
self.document = document
self.position = position
self.length = length
self.deleted_text = ""
def execute(self):
self.deleted_text = self.document.delete(self.position, self.length)
def undo(self):
self.document.insert(self.deleted_text, self.position)
class Editor:
"""The Invoker -- triggers commands and manages history."""
def __init__(self, document: TextDocument):
self.document = document
self.history: list[Command] = []
self.redo_stack: list[Command] = []
def execute(self, command: Command):
command.execute()
self.history.append(command)
self.redo_stack.clear()
def undo(self):
if not self.history:
return
cmd = self.history.pop()
cmd.undo()
self.redo_stack.append(cmd)
def redo(self):
if not self.redo_stack:
return
cmd = self.redo_stack.pop()
cmd.execute()
self.history.append(cmd)
# Usage
doc = TextDocument()
editor = Editor(doc)
editor.execute(InsertCommand(doc, "Hello", 0))
print(doc.content) # "Hello"
editor.execute(InsertCommand(doc, " World", 5))
print(doc.content) # "Hello World"
editor.undo()
print(doc.content) # "Hello"
editor.redo()
print(doc.content) # "Hello World"
class TextDocument {
constructor() {
this.content = "";
}
insert(text, position) {
this.content = this.content.slice(0, position) + text + this.content.slice(position);
}
delete(position, length) {
const deleted = this.content.slice(position, position + length);
this.content = this.content.slice(0, position) + this.content.slice(position + length);
return deleted;
}
}
class InsertCommand {
constructor(document, text, position) {
this.document = document;
this.text = text;
this.position = position;
}
execute() {
this.document.insert(this.text, this.position);
}
undo() {
this.document.delete(this.position, this.text.length);
}
}
class DeleteCommand {
constructor(document, position, length) {
this.document = document;
this.position = position;
this.length = length;
this.deletedText = "";
}
execute() {
this.deletedText = this.document.delete(this.position, this.length);
}
undo() {
this.document.insert(this.deletedText, this.position);
}
}
class Editor {
constructor(document) {
this.document = document;
this.history = [];
this.redoStack = [];
}
execute(command) {
command.execute();
this.history.push(command);
this.redoStack = [];
}
undo() {
if (this.history.length === 0) return;
const cmd = this.history.pop();
cmd.undo();
this.redoStack.push(cmd);
}
redo() {
if (this.redoStack.length === 0) return;
const cmd = this.redoStack.pop();
cmd.execute();
this.history.push(cmd);
}
}
// Usage
const doc = new TextDocument();
const editor = new Editor(doc);
editor.execute(new InsertCommand(doc, "Hello", 0));
console.log(doc.content); // "Hello"
editor.execute(new InsertCommand(doc, " World", 5));
console.log(doc.content); // "Hello World"
editor.undo();
console.log(doc.content); // "Hello"
import java.util.Stack;
interface Command {
void execute();
void undo();
}
class TextDocument {
StringBuilder content = new StringBuilder();
void insert(String text, int position) {
content.insert(position, text);
}
String delete(int position, int length) {
String deleted = content.substring(position, position + length);
content.delete(position, position + length);
return deleted;
}
String getContent() { return content.toString(); }
}
class InsertCommand implements Command {
private TextDocument document;
private String text;
private int position;
InsertCommand(TextDocument doc, String text, int position) {
this.document = doc;
this.text = text;
this.position = position;
}
public void execute() { document.insert(text, position); }
public void undo() { document.delete(position, text.length()); }
}
class DeleteCommand implements Command {
private TextDocument document;
private int position, length;
private String deletedText = "";
DeleteCommand(TextDocument doc, int position, int length) {
this.document = doc;
this.position = position;
this.length = length;
}
public void execute() { deletedText = document.delete(position, length); }
public void undo() { document.insert(deletedText, position); }
}
class Editor {
private TextDocument document;
private Stack<Command> history = new Stack<>();
private Stack<Command> redoStack = new Stack<>();
Editor(TextDocument document) { this.document = document; }
void execute(Command cmd) {
cmd.execute();
history.push(cmd);
redoStack.clear();
}
void undo() {
if (history.isEmpty()) return;
Command cmd = history.pop();
cmd.undo();
redoStack.push(cmd);
}
void redo() {
if (redoStack.isEmpty()) return;
Command cmd = redoStack.pop();
cmd.execute();
history.push(cmd);
}
}
When to Use
- Undo/Redo — text editors, drawing apps, any operation we need to reverse
- Task queues — schedule commands to run later, retry on failure
- Macro recording — record a sequence of commands and replay them
- Transaction management — execute a batch, roll back everything if one fails
- Remote control / smart home — “turn on lights” is a command object
When NOT to Use
- Simple direct calls that will never need undo, queuing, or logging
- When the command just wraps a single method call with no extra behavior — that’s pointless indirection
- When we don’t need any of the benefits (undo, queue, log) — it’s just extra classes
Common Interview Questions
Q: How does Command enable macro recording?
Store a list of executed commands. To replay the macro, just loop through the list and call execute() on each one. Simple as that.
Q: How do we implement transactions with Command?
Execute commands in sequence. If any fails, call undo() on all previously executed commands in reverse order. Same idea as database rollback.
In simple language, Command is like writing down a task on a sticky note instead of doing it immediately. Once it’s written down, we can stick it on a board (queue), throw it away (cancel), or unstick it and reverse what it did (undo). The sticky note IS the command.