Command Pattern: Behavioral Design Guide
TL;DR
The Command Pattern encapsulates a request as an object, allowing you to parameterize clients with different requests, queue operations, log requests, and support undo/redo functionality. It decouples the object that invokes the operation from the one that knows how to perform it.
Core Concept
What is the Command Pattern?
The Command Pattern is a behavioral design pattern that turns a request into a stand-alone object containing all information about the request. This transformation lets you pass requests as method arguments, delay or queue a request’s execution, and support undoable operations.
Core Components
The pattern consists of four key players:
- Command Interface: Declares an
execute()method (and optionallyundo()). - Concrete Command: Implements the Command interface and defines the binding between a Receiver and an action.
- Receiver: The object that performs the actual work when the command’s
execute()method is called. - Invoker: Asks the command to carry out the request. It doesn’t know what the command does or who the receiver is.
- Client: Creates concrete command objects and sets their receivers.
Why Use the Command Pattern?
The Command Pattern solves several important problems:
Decoupling: The invoker doesn’t need to know anything about the receiver or the operation being performed. It just calls execute() on a command object.
Undo/Redo: By storing executed commands in a history stack, you can easily implement undo by calling an undo() method that reverses the operation.
Queuing and Scheduling: Commands can be stored in queues and executed later, enabling task scheduling, job queues, and transactional systems.
Macro Commands: You can create composite commands that execute multiple commands in sequence, useful for scripting and batch operations.
Logging and Auditing: Since commands are objects, you can log them for audit trails or crash recovery.
When to Use It
Consider the Command Pattern when you need to:
- Parameterize objects with operations (like GUI buttons that perform different actions)
- Queue operations for later execution
- Support undo/redo functionality
- Log changes so you can reapply them after a system crash
- Structure a system around high-level operations built on primitive operations
Visual Guide
Command Pattern Structure
classDiagram
class Command {
<<interface>>
+execute()
+undo()
}
class ConcreteCommand {
-receiver: Receiver
-state
+execute()
+undo()
}
class Receiver {
+action()
}
class Invoker {
-command: Command
+setCommand(cmd)
+executeCommand()
}
class Client
Command <|.. ConcreteCommand
ConcreteCommand --> Receiver
Invoker --> Command
Client --> ConcreteCommand
Client --> Receiver
Client --> Invoker
The Command Pattern decouples the invoker from the receiver through a command interface. The client creates concrete commands and wires them to receivers.
Command Pattern Flow
sequenceDiagram
participant Client
participant Invoker
participant Command
participant Receiver
Client->>Command: create(receiver)
Client->>Invoker: setCommand(command)
Client->>Invoker: executeCommand()
Invoker->>Command: execute()
Command->>Receiver: action()
Receiver-->>Command: result
Command-->>Invoker: done
Execution flow showing how the client sets up the command, the invoker triggers it, and the command delegates to the receiver.
Examples
Example 1: Smart Home Light Control with Undo
Let’s build a smart home system where we can control lights and undo operations.
from abc import ABC, abstractmethod
# Receiver: The object that knows how to perform the actual work
class Light:
def __init__(self, location):
self.location = location
self.is_on = False
self.brightness = 0
def turn_on(self):
self.is_on = True
self.brightness = 100
print(f"{self.location} light is ON at {self.brightness}% brightness")
def turn_off(self):
self.is_on = False
self.brightness = 0
print(f"{self.location} light is OFF")
def dim(self, level):
if self.is_on:
self.brightness = level
print(f"{self.location} light dimmed to {level}%")
else:
print(f"{self.location} light is off, cannot dim")
# Command Interface
class Command(ABC):
@abstractmethod
def execute(self):
pass
@abstractmethod
def undo(self):
pass
# Concrete Commands
class LightOnCommand(Command):
def __init__(self, light):
self.light = light
self.previous_state = None
def execute(self):
self.previous_state = self.light.is_on
self.light.turn_on()
def undo(self):
if not self.previous_state:
self.light.turn_off()
class LightOffCommand(Command):
def __init__(self, light):
self.light = light
self.previous_state = None
def execute(self):
self.previous_state = self.light.is_on
self.light.turn_off()
def undo(self):
if self.previous_state:
self.light.turn_on()
class DimLightCommand(Command):
def __init__(self, light, level):
self.light = light
self.level = level
self.previous_brightness = None
def execute(self):
self.previous_brightness = self.light.brightness
self.light.dim(self.level)
def undo(self):
self.light.dim(self.previous_brightness)
# Invoker: Remote control that can execute commands and undo them
class RemoteControl:
def __init__(self):
self.history = []
def execute_command(self, command):
command.execute()
self.history.append(command)
def undo_last(self):
if self.history:
command = self.history.pop()
command.undo()
else:
print("Nothing to undo")
# Client code
if __name__ == "__main__":
# Create receivers
living_room_light = Light("Living Room")
bedroom_light = Light("Bedroom")
# Create invoker
remote = RemoteControl()
# Create and execute commands
print("=== Turning on lights ===")
remote.execute_command(LightOnCommand(living_room_light))
remote.execute_command(LightOnCommand(bedroom_light))
print("\n=== Dimming living room ===")
remote.execute_command(DimLightCommand(living_room_light, 50))
print("\n=== Undo last operation ===")
remote.undo_last() # Undo dim
print("\n=== Undo again ===")
remote.undo_last() # Undo bedroom light on
print("\n=== Turn off living room ===")
remote.execute_command(LightOffCommand(living_room_light))
Expected Output:
=== Turning on lights ===
Living Room light is ON at 100% brightness
Bedroom light is ON at 100% brightness
=== Dimming living room ===
Living Room light dimmed to 50%
=== Undo last operation ===
Living Room light dimmed to 100%
=== Undo again ===
Bedroom light is OFF
=== Turn off living room ===
Living Room light is OFF
Try it yourself: Add a MacroCommand class that can execute multiple commands at once (e.g., “Movie Mode” that dims lights and closes blinds).
Example 2: Text Editor with Undo/Redo
A practical example showing how commands enable undo/redo in a text editor.
from abc import ABC, abstractmethod
from typing import List
# Receiver: The document being edited
class Document:
def __init__(self):
self.content = ""
def insert(self, position, text):
self.content = self.content[:position] + text + self.content[position:]
print(f"Inserted '{text}' at position {position}")
print(f"Content: '{self.content}'")
def delete(self, position, length):
deleted = self.content[position:position + length]
self.content = self.content[:position] + self.content[position + length:]
print(f"Deleted '{deleted}' from position {position}")
print(f"Content: '{self.content}'")
return deleted
def get_content(self):
return self.content
# Command Interface
class EditCommand(ABC):
@abstractmethod
def execute(self):
pass
@abstractmethod
def undo(self):
pass
# Concrete Commands
class InsertCommand(EditCommand):
def __init__(self, document, position, text):
self.document = document
self.position = position
self.text = text
def execute(self):
self.document.insert(self.position, self.text)
def undo(self):
self.document.delete(self.position, len(self.text))
class DeleteCommand(EditCommand):
def __init__(self, document, position, length):
self.document = document
self.position = position
self.length = length
self.deleted_text = None
def execute(self):
self.deleted_text = self.document.delete(self.position, self.length)
def undo(self):
if self.deleted_text:
self.document.insert(self.position, self.deleted_text)
# Invoker: Editor with undo/redo stacks
class TextEditor:
def __init__(self, document):
self.document = document
self.undo_stack: List[EditCommand] = []
self.redo_stack: List[EditCommand] = []
def execute(self, command):
command.execute()
self.undo_stack.append(command)
# Clear redo stack when new command is executed
self.redo_stack.clear()
def undo(self):
if self.undo_stack:
command = self.undo_stack.pop()
command.undo()
self.redo_stack.append(command)
print("Undo completed")
else:
print("Nothing to undo")
def redo(self):
if self.redo_stack:
command = self.redo_stack.pop()
command.execute()
self.undo_stack.append(command)
print("Redo completed")
else:
print("Nothing to redo")
# Client code
if __name__ == "__main__":
doc = Document()
editor = TextEditor(doc)
print("=== Initial edits ===")
editor.execute(InsertCommand(doc, 0, "Hello"))
editor.execute(InsertCommand(doc, 5, " World"))
editor.execute(InsertCommand(doc, 11, "!"))
print("\n=== Undo twice ===")
editor.undo()
editor.undo()
print("\n=== Redo once ===")
editor.redo()
print("\n=== New edit (clears redo stack) ===")
editor.execute(DeleteCommand(doc, 5, 6)) # Delete " World"
print("\n=== Try to redo (should fail) ===")
editor.redo()
print("\n=== Undo delete ===")
editor.undo()
Expected Output:
=== Initial edits ===
Inserted 'Hello' at position 0
Content: 'Hello'
Inserted ' World' at position 5
Content: 'Hello World'
Inserted '!' at position 11
Content: 'Hello World!'
=== Undo twice ===
Deleted '!' from position 11
Content: 'Hello World'
Undo completed
Deleted ' World' from position 5
Content: 'Hello'
Undo completed
=== Redo once ===
Inserted ' World' at position 5
Content: 'Hello World'
Redo completed
=== New edit (clears redo stack) ===
Deleted ' World' from position 5
Content: 'Hello'
=== Try to redo (should fail) ===
Nothing to redo
=== Undo delete ===
Inserted ' World' at position 5
Content: 'Hello World'
Undo completed
Try it yourself: Implement a ReplaceCommand that combines delete and insert operations, ensuring both are undone together.
Java/C++ Notes
Java: Use interfaces for the Command abstraction. The pattern is identical in structure.
public interface Command {
void execute();
void undo();
}
public class LightOnCommand implements Command {
private Light light;
private boolean previousState;
public LightOnCommand(Light light) {
this.light = light;
}
public void execute() {
previousState = light.isOn();
light.turnOn();
}
public void undo() {
if (!previousState) {
light.turnOff();
}
}
}
C++: Use abstract base classes with virtual methods. Remember to use smart pointers for memory management.
class Command {
public:
virtual ~Command() = default;
virtual void execute() = 0;
virtual void undo() = 0;
};
class LightOnCommand : public Command {
private:
Light* light;
bool previousState;
public:
LightOnCommand(Light* l) : light(l), previousState(false) {}
void execute() override {
previousState = light->isOn();
light->turnOn();
}
void undo() override {
if (!previousState) {
light->turnOff();
}
}
};
Common Mistakes
1. Not Storing Enough State for Undo
Mistake: Forgetting to save the previous state before executing a command, making undo impossible or incorrect.
# WRONG: No state saved
class BadDimCommand(Command):
def __init__(self, light, level):
self.light = light
self.level = level
# Missing: self.previous_brightness
def execute(self):
self.light.dim(self.level) # Lost previous brightness!
def undo(self):
# What value to restore? We don't know!
pass
Fix: Always capture the state you need to reverse the operation before executing it.
# CORRECT: State saved
class GoodDimCommand(Command):
def __init__(self, light, level):
self.light = light
self.level = level
self.previous_brightness = None
def execute(self):
self.previous_brightness = self.light.brightness # Save state
self.light.dim(self.level)
def undo(self):
self.light.dim(self.previous_brightness) # Restore state
2. Mixing Business Logic with Command Logic
Mistake: Putting business logic directly in the command instead of delegating to the receiver.
# WRONG: Command contains business logic
class BadTransferCommand(Command):
def __init__(self, from_account, to_account, amount):
self.from_account = from_account
self.to_account = to_account
self.amount = amount
def execute(self):
# Business logic in command - hard to reuse and test
if self.from_account.balance >= self.amount:
self.from_account.balance -= self.amount
self.to_account.balance += self.amount
else:
raise ValueError("Insufficient funds")
Fix: Commands should be thin wrappers that delegate to receivers.
# CORRECT: Command delegates to receiver
class BankAccount:
def transfer_to(self, other_account, amount):
if self.balance >= amount:
self.balance -= amount
other_account.balance += amount
else:
raise ValueError("Insufficient funds")
class GoodTransferCommand(Command):
def __init__(self, from_account, to_account, amount):
self.from_account = from_account
self.to_account = to_account
self.amount = amount
def execute(self):
self.from_account.transfer_to(self.to_account, self.amount)
3. Not Clearing Redo Stack on New Command
Mistake: Allowing redo after a new command is executed, leading to inconsistent state.
# WRONG: Redo stack not cleared
class BadEditor:
def execute(self, command):
command.execute()
self.undo_stack.append(command)
# Missing: self.redo_stack.clear()
# User does: type "A", undo, type "B", redo
# Result: "AB" (incorrect - should be just "B")
Fix: Clear the redo stack whenever a new command is executed.
# CORRECT: Redo stack cleared
class GoodEditor:
def execute(self, command):
command.execute()
self.undo_stack.append(command)
self.redo_stack.clear() # Clear redo history
4. Creating Too Many Command Classes
Mistake: Creating a separate command class for every tiny operation, leading to class explosion.
Fix: Use parameterized commands or lambda functions for simple operations.
# Instead of: TurnOnCommand, TurnOffCommand, DimTo50Command, DimTo75Command...
# Use a parameterized command:
class LightCommand(Command):
def __init__(self, light, action, *args):
self.light = light
self.action = action
self.args = args
self.previous_state = None
def execute(self):
self.previous_state = (self.light.is_on, self.light.brightness)
self.action(*self.args)
def undo(self):
# Restore previous state
pass
# Usage:
command = LightCommand(light, light.dim, 50)
5. Not Handling Command Failure
Mistake: Not considering what happens when a command fails during execution.
# WRONG: No error handling
class RiskyCommand(Command):
def execute(self):
self.receiver.risky_operation() # Might fail!
self.history.append(self) # Added even if operation failed
Fix: Handle failures and only add to history if successful.
# CORRECT: Error handling
class SafeCommand(Command):
def execute(self):
try:
self.receiver.risky_operation()
return True # Success
except Exception as e:
print(f"Command failed: {e}")
return False # Failure
class Invoker:
def execute_command(self, command):
if command.execute(): # Only add to history if successful
self.history.append(command)
Interview Tips
What Interviewers Look For
1. Recognize When to Use the Pattern
Interviewers often present scenarios and ask you to identify the appropriate pattern. Command Pattern red flags include:
- “Design a system that supports undo/redo”
- “Queue operations for later execution”
- “Log all user actions for audit”
- “Implement macro commands or scripting”
When you hear these, immediately think Command Pattern and explain why it fits.
2. Explain the Four Key Benefits
Be ready to articulate these advantages clearly:
- Decoupling: Invoker doesn’t know about receivers
- Undo/Redo: Commands store state for reversal
- Queuing: Commands are objects that can be stored and executed later
- Composability: Macro commands combine multiple operations
3. Implement Undo Correctly
This is the most common interview question. Show you understand:
- State must be captured BEFORE execution
- Undo reverses the operation using saved state
- Redo re-executes the command
- New commands clear the redo stack
Code this pattern from memory:
class Command:
def execute(self):
self.previous_state = self.receiver.get_state()
self.receiver.perform_action()
def undo(self):
self.receiver.set_state(self.previous_state)
4. Compare with Strategy Pattern
Interviewers love asking: “How is Command different from Strategy?”
Answer: Both encapsulate operations, but:
- Command: Encapsulates a request with its receiver and parameters. Supports undo, queuing, logging. The command object has a lifecycle.
- Strategy: Encapsulates an algorithm. Focuses on interchangeable behaviors. No undo or queuing.
Example: A text editor uses Command for edit operations (undo needed) but Strategy for spell-checking algorithms (no undo needed).
5. Discuss Trade-offs
Show maturity by discussing when NOT to use the pattern:
- Overhead: Creates many small classes for simple operations
- Complexity: Overkill for systems that don’t need undo or queuing
- Memory: Storing command history consumes memory
Alternative: For simple callbacks without undo, use function pointers or lambdas instead.
Common Interview Questions
Q: “Design a text editor with undo/redo.”
- Start with Command interface (execute, undo)
- Create InsertCommand and DeleteCommand
- Show how Editor maintains undo/redo stacks
- Demonstrate clearing redo stack on new command
Q: “How would you implement a macro command?”
class MacroCommand(Command):
def __init__(self, commands):
self.commands = commands
def execute(self):
for cmd in self.commands:
cmd.execute()
def undo(self):
# Undo in reverse order
for cmd in reversed(self.commands):
cmd.undo()
Q: “What if a command in a macro fails?” Discuss transaction-like behavior:
- Execute all commands
- If any fails, undo all previous commands in reverse
- Use try-catch and maintain executed list
Q: “How do you handle commands that can’t be undone?”
- Create a
NonUndoableCommandbase class - Or: Have
undo()throw an exception - Or: Return a boolean from
canUndo() - Disable undo button in UI when such commands are executed
Code Interview Strategy
- Start simple: Implement basic Command interface and one concrete command
- Add undo: Show state management
- Add invoker: Demonstrate decoupling
- Extend: Add redo, macro commands, or queuing based on requirements
Don’t try to build everything at once. Interviewers want to see incremental design.
Key Takeaways
- Command Pattern encapsulates requests as objects, enabling parameterization, queuing, logging, and undo/redo functionality by decoupling the invoker from the receiver.
- Undo requires capturing state before execution — always save the previous state in the command object before performing the operation so you can reverse it later.
- Commands delegate to receivers — keep commands thin by having them call methods on receiver objects rather than implementing business logic directly.
- Clear the redo stack on new commands — when a user executes a new command after undoing, the redo history becomes invalid and must be cleared to maintain consistency.
- Use the pattern when you need operation queuing, logging, undo/redo, or macro commands — but avoid it for simple callbacks where the overhead of creating command classes isn’t justified.