Command
Encapsulates a request as an object, allowing parameterization and queuing.
Command Pattern: A Complete Guide with Examples in 11 Programming Languages
The Command Pattern is a behavioral design pattern that turns a request or action into a standalone object. By encapsulating everything needed to perform an operation — the receiver, the method to call, and the arguments — into a command object, you gain the ability to parameterize actions, queue them for later execution, log them for auditing, support undo/redo, and compose them into macro commands. It is one of the most broadly applicable patterns in software engineering, underpinning everything from GUI toolbars and text editors to transactional systems, job queues, and event sourcing.
In this comprehensive guide, we'll explore the Command Pattern with real-world examples in Python, TypeScript, Java, JavaScript, C#, PHP, Go, Rust, Dart, Swift, and Kotlin — complete with language-specific best practices, common pitfalls, and when to use this powerful pattern.
Table of Contents
- What is the Command Pattern?
- Why Use the Command Pattern?
- Command Pattern Comparison
- Command Pattern Explained
- Class Diagram
- Beginner-Friendly Example
- Production-Ready Example
- Real-World Use Cases
- Language-Specific Mistakes and Anti-Patterns
- Frequently Asked Questions
- Key Takeaways
What is the Command Pattern?
The Command Pattern converts a method call into an object. Instead of calling receiver.doSomething(args) directly, you create a DoSomethingCommand(receiver, args) object and pass it around. The object can be executed now, stored for later, queued, serialized, logged, or reversed.
Think of it like a restaurant order slip. A waiter doesn't walk into the kitchen and cook the meal himself. He writes the order on a slip (the command object) and hands it to the kitchen. The kitchen processes slips in order. The slip can be put on hold, cancelled, or replicated for the next table. Neither the waiter nor the customer cooks — the order slip is what gets passed around and eventually executed.
Why Use the Command Pattern?
The Command Pattern offers several compelling benefits:
- Undo / Redo: Commands store everything needed to reverse an operation — enabling multi-level undo in editors, IDEs, and creative tools
- Deferred Execution: Commands can be queued and executed later — at the right time, by the right thread, under the right conditions
- Macro Commands: Composite commands group multiple operations into a single atomic action
- Logging and Audit Trails: Every executed command is an object that can be persisted, serialized, and replayed
- Transactional Behavior: If a compound operation fails halfway, stored commands can be rolled back in reverse order
- Parameterization: Buttons, menu items, and hotkeys can all be wired to different command objects without changing the UI code
- Job Queues / Task Scheduling: Commands are natural units of work for background processors and async job systems
Command Pattern Comparison
Let's compare the Command Pattern with related behavioral patterns:
| Pattern | Encapsulates | Relationship to Receiver | Key Capability | Use Case |
|---|---|---|---|---|
| Command | A request as an object | Holds a reference to receiver | Undo, queuing, logging, macro | Editor actions, job queues, transactions |
| Strategy | An algorithm | IS the algorithm | Swappable behavior | Sorting, pricing, compression |
| Chain of Responsibility | A handler decision | Passes to next handler | Dynamic routing of requests | Middleware, escalation |
| Observer | A subscription | Notified on event | Broadcast to many | Event systems, reactive UI |
| Template Method | An algorithm skeleton | Defined in base class | Step customization | Frameworks, report generation |
Key Distinctions:
- Command vs. Strategy: Both encapsulate behavior in an object. Strategy encapsulates a how (algorithm), Command encapsulates a what (a specific action with specific arguments on a specific receiver). Commands are typically used once; Strategies are swapped in and out.
- Command vs. Callback/Lambda: A lambda is a lightweight command. The full Command pattern adds undo support, serialization, and metadata that a plain function can't carry.
Command Pattern Explained
The Command Pattern typically involves:
Core Components:
- Command Interface: Declares the
execute()method and, optionally, anundo()method. Every command in the system implements this interface. - Concrete Command: Implements
execute()andundo(). Holds a reference to the Receiver and the parameters needed to perform the action. Delegates actual work to the receiver. - Receiver: The object that knows how to perform the actual operation. Contains the real business logic. The command calls methods on it.
- Invoker: Asks the command to carry out a request. Doesn't know the concrete command type or what it does — just calls
execute(). May also manage a history stack for undo/redo. - Client: Creates concrete command objects and configures them with a receiver, then hands them to an invoker.
Common Command Variants:
- Simple Command: Wraps a single receiver method call with its arguments.
- Macro Command: A composite that executes multiple commands in sequence. Can undo them in reverse order.
- Undoable Command: Implements
undo()to reverse the effect ofexecute(). - Transactional Command: Part of a larger transaction; if any command fails, all are rolled back.
- Async Command: Execution is deferred and happens on a thread pool or event loop.
- Serializable Command: Can be converted to JSON/binary and stored or transmitted, then reconstructed and replayed.
Class Diagram
Here's the UML class diagram showing the Command structure:
Beginner-Friendly Example
Let's start with a classic example: a smart home controller. Lights, fans, and thermostats can all be controlled by buttons on a remote. Each button is assigned a command — pressing it executes that command, and pressing "undo" reverses the last action.
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import Optional
# Command Interface
class Command(ABC):
@abstractmethod
def execute(self) -> None:
pass
@abstractmethod
def undo(self) -> None:
pass
def description(self) -> str:
return self.__class__.__name__
# Receivers
class Light:
def __init__(self, location: str) -> None:
self.location = location
self._on = False
self._brightness = 100
def turn_on(self) -> None:
self._on = True
print(f" [Light:{self.location}] Turned ON (brightness={self._brightness}%)")
def turn_off(self) -> None:
self._on = False
print(f" [Light:{self.location}] Turned OFF")
def set_brightness(self, level: int) -> None:
self._brightness = level
print(f" [Light:{self.location}] Brightness set to {level}%")
@property
def is_on(self) -> bool:
return self._on
@property
def brightness(self) -> int:
return self._brightness
class Fan:
SPEEDS = ["off", "low", "medium", "high"]
def __init__(self, location: str) -> None:
self.location = location
self._speed = "off"
def set_speed(self, speed: str) -> None:
self._speed = speed
print(f" [Fan:{self.location}] Speed set to '{speed}'")
@property
def speed(self) -> str:
return self._speed
# Concrete Commands
class LightOnCommand(Command):
def __init__(self, light: Light) -> None:
self._light = light
def execute(self) -> None:
self._light.turn_on()
def undo(self) -> None:
self._light.turn_off()
def description(self) -> str:
return f"LightOn({self._light.location})"
class LightOffCommand(Command):
def __init__(self, light: Light) -> None:
self._light = light
def execute(self) -> None:
self._light.turn_off()
def undo(self) -> None:
self._light.turn_on()
def description(self) -> str:
return f"LightOff({self._light.location})"
class SetBrightnessCommand(Command):
def __init__(self, light: Light, level: int) -> None:
self._light = light
self._level = level
self._prev_level: Optional[int] = None
self._prev_on: Optional[bool] = None
def execute(self) -> None:
# Save state before executing so undo can restore it
self._prev_level = self._light.brightness
self._prev_on = self._light.is_on
self._light.set_brightness(self._level)
if not self._light.is_on:
self._light.turn_on()
def undo(self) -> None:
if self._prev_level is not None:
self._light.set_brightness(self._prev_level)
if self._prev_on is False:
self._light.turn_off()
def description(self) -> str:
return f"SetBrightness({self._light.location}, {self._level}%)"
class FanSpeedCommand(Command):
def __init__(self, fan: Fan, speed: str) -> None:
self._fan = fan
self._speed = speed
self._prev_speed: Optional[str] = None
def execute(self) -> None:
self._prev_speed = self._fan.speed
self._fan.set_speed(self._speed)
def undo(self) -> None:
if self._prev_speed is not None:
self._fan.set_speed(self._prev_speed)
def description(self) -> str:
return f"FanSpeed({self._fan.location}, {self._speed})"
class MacroCommand(Command):
"""Composite command — executes multiple commands as one atomic action."""
def __init__(self, name: str, commands: list[Command]) -> None:
self._name = name
self._commands = commands
def execute(self) -> None:
print(f" [Macro:{self._name}] Executing {len(self._commands)} commands:")
for cmd in self._commands:
cmd.execute()
def undo(self) -> None:
print(f" [Macro:{self._name}] Undoing {len(self._commands)} commands:")
for cmd in reversed(self._commands):
cmd.undo()
def description(self) -> str:
return f"Macro({self._name})"
class NoOpCommand(Command):
"""Null Object — a command that does nothing (safe default for unbound buttons)."""
def execute(self) -> None:
pass
def undo(self) -> None:
pass
def description(self) -> str:
return "NoOp"
# Invoker
class RemoteControl:
SLOTS = 7
def __init__(self) -> None:
no_op = NoOpCommand()
self._on_commands: list[Command] = [no_op] * self.SLOTS
self._off_commands: list[Command] = [no_op] * self.SLOTS
self._history: list[Command] = []
def set_command(self, slot: int, on_cmd: Command, off_cmd: Command) -> None:
self._on_commands[slot] = on_cmd
self._off_commands[slot] = off_cmd
def press_on(self, slot: int) -> None:
cmd = self._on_commands[slot]
print(f"[Remote] ON slot={slot} → {cmd.description()}")
cmd.execute()
self._history.append(cmd)
def press_off(self, slot: int) -> None:
cmd = self._off_commands[slot]
print(f"[Remote] OFF slot={slot} → {cmd.description()}")
cmd.execute()
self._history.append(cmd)
def press_undo(self) -> None:
if not self._history:
print("[Remote] Nothing to undo")
return
cmd = self._history.pop()
print(f"[Remote] UNDO → {cmd.description()}")
cmd.undo()
def history_log(self) -> None:
print("\n[Remote] Command history:")
for i, cmd in enumerate(self._history, 1):
print(f" {i}. {cmd.description()}")
# Client
if __name__ == "__main__":
# Receivers
living_light = Light("Living Room")
bedroom_light = Light("Bedroom")
ceiling_fan = Fan("Living Room")
# Commands
living_on = LightOnCommand(living_light)
living_off = LightOffCommand(living_light)
bedroom_on = LightOnCommand(bedroom_light)
bedroom_off = LightOffCommand(bedroom_light)
dim_living = SetBrightnessCommand(living_light, 30)
fan_high = FanSpeedCommand(ceiling_fan, "high")
fan_off = FanSpeedCommand(ceiling_fan, "off")
# Movie mode macro: dim lights + fan on
movie_on = MacroCommand("MovieOn", [dim_living, fan_high])
movie_off = MacroCommand("MovieOff", [living_on, fan_off])
# Configure remote
remote = RemoteControl()
remote.set_command(0, living_on, living_off)
remote.set_command(1, bedroom_on, bedroom_off)
remote.set_command(2, fan_high, fan_off)
remote.set_command(3, movie_on, movie_off)
print("=== Smart Home Remote ===\n")
print("--- Press ON for Living Room light ---")
remote.press_on(0)
print("\n--- Press ON for Bedroom light ---")
remote.press_on(1)
print("\n--- Activate Movie Mode ---")
remote.press_on(3)
print("\n--- Undo Movie Mode ---")
remote.press_undo()
print("\n--- Undo again (bedroom light) ---")
remote.press_undo()
remote.history_log()
Production-Ready Example
Now let's tackle a realistic scenario: a transactional order management system. Processing an order involves multiple steps — reserving inventory, charging payment, creating a shipment. Each step is a command. If any step fails, previously executed commands roll back in reverse order. Completed commands are logged for audit and can be replayed.
🐍 Python (Production Example)
from __future__ import annotations
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from datetime import datetime
from typing import Optional, List
import uuid
# ─── Domain Models ────────────────────────────────────────────────
@dataclass
class OrderItem:
product_id: str
quantity: int
unit_price: float
@dataclass
class Order:
id: str = field(default_factory=lambda: str(uuid.uuid4())[:8])
customer_id:str = ""
items: List[OrderItem] = field(default_factory=list)
status: str = "pending"
@property
def total(self) -> float:
return sum(i.quantity * i.unit_price for i in self.items)
# ─── Receivers ─────────────────────────────────────────────────────
class InventoryService:
def __init__(self):
self._stock = {"P001": 50, "P002": 20, "P003": 5}
self._reservations: dict[str, dict] = {}
def reserve(self, order_id: str, product_id: str, qty: int) -> str:
available = self._stock.get(product_id, 0)
if available < qty:
raise RuntimeError(f"Insufficient stock for {product_id}: have {available}, need {qty}")
self._stock[product_id] -= qty
reservation_id = f"RES-{uuid.uuid4().hex[:6].upper()}"
self._reservations[reservation_id] = {"order_id": order_id, "product_id": product_id, "qty": qty}
print(f" [Inventory] Reserved {qty}x {product_id} → reservation={reservation_id}")
return reservation_id
def release(self, reservation_id: str) -> None:
res = self._reservations.pop(reservation_id, None)
if res:
self._stock[res["product_id"]] += res["qty"]
print(f" [Inventory] Released reservation={reservation_id}")
def stock(self, product_id: str) -> int:
return self._stock.get(product_id, 0)
class PaymentService:
def charge(self, customer_id: str, amount: float, order_id: str) -> str:
if amount <= 0:
raise ValueError("Amount must be positive")
txn_id = f"TXN-{uuid.uuid4().hex[:6].upper()}"
print(f" [Payment] Charged ${amount:.2f} to customer={customer_id} → txn={txn_id}")
return txn_id
def refund(self, txn_id: str, amount: float) -> None:
print(f" [Payment] Refunded ${amount:.2f} for txn={txn_id}")
class ShippingService:
def create_shipment(self, order_id: str, customer_id: str) -> str:
tracking = f"TRACK-{uuid.uuid4().hex[:6].upper()}"
print(f" [Shipping] Created shipment for order={order_id} → tracking={tracking}")
return tracking
def cancel_shipment(self, tracking: str) -> None:
print(f" [Shipping] Cancelled shipment={tracking}")
class AuditLog:
def __init__(self):
self._entries: list[dict] = []
def record(self, event: str, details: dict) -> None:
entry = {"ts": datetime.now().isoformat(timespec="seconds"), "event": event, **details}
self._entries.append(entry)
print(f" [Audit] {entry['ts']} | {event} | {details}")
def replay(self) -> None:
print("\n[Audit] Full log:")
for e in self._entries:
print(f" {e}")
# ─── Command Interface ─────────────────────────────────────────────
class OrderCommand(ABC):
def __init__(self, order: Order):
self._order = order
self._executed = False
@abstractmethod
def execute(self) -> None:
pass
@abstractmethod
def undo(self) -> None:
pass
@property
def description(self) -> str:
return self.__class__.__name__
# ─── Concrete Commands ─────────────────────────────────────────────
class ReserveInventoryCommand(OrderCommand):
def __init__(self, order: Order, inventory: InventoryService):
super().__init__(order)
self._inventory = inventory
self._reservations: list[str] = []
def execute(self) -> None:
for item in self._order.items:
res_id = self._inventory.reserve(self._order.id, item.product_id, item.quantity)
self._reservations.append(res_id)
self._executed = True
def undo(self) -> None:
if self._executed:
for res_id in reversed(self._reservations):
self._inventory.release(res_id)
self._reservations.clear()
self._executed = False
@property
def description(self) -> str:
return f"ReserveInventory(order={self._order.id})"
class ChargePaymentCommand(OrderCommand):
def __init__(self, order: Order, payment: PaymentService):
super().__init__(order)
self._payment = payment
self._txn_id: Optional[str] = None
def execute(self) -> None:
self._txn_id = self._payment.charge(self._order.customer_id, self._order.total, self._order.id)
self._executed = True
def undo(self) -> None:
if self._executed and self._txn_id:
self._payment.refund(self._txn_id, self._order.total)
self._txn_id = None
self._executed = False
@property
def description(self) -> str:
return f"ChargePayment(order={self._order.id}, total=${self._order.total:.2f})"
class CreateShipmentCommand(OrderCommand):
def __init__(self, order: Order, shipping: ShippingService):
super().__init__(order)
self._shipping = shipping
self._tracking: Optional[str] = None
def execute(self) -> None:
self._tracking = self._shipping.create_shipment(self._order.id, self._order.customer_id)
self._order.status = "shipped"
self._executed = True
def undo(self) -> None:
if self._executed and self._tracking:
self._shipping.cancel_shipment(self._tracking)
self._order.status = "pending"
self._tracking = None
self._executed = False
@property
def description(self) -> str:
return f"CreateShipment(order={self._order.id})"
class AuditCommand(OrderCommand):
"""Decorator-style command: wraps another command and logs it."""
def __init__(self, inner: OrderCommand, audit: AuditLog):
super().__init__(inner._order)
self._inner = inner
self._audit = audit
def execute(self) -> None:
self._inner.execute()
self._audit.record("EXECUTED", {"command": self._inner.description})
def undo(self) -> None:
self._inner.undo()
self._audit.record("UNDONE", {"command": self._inner.description})
@property
def description(self) -> str:
return f"Audited({self._inner.description})"
# ─── Invoker: Transactional Command Processor ─────────────────────
class TransactionalProcessor:
"""
Executes a list of commands in order.
On failure, rolls back all previously executed commands in reverse order.
"""
def __init__(self):
self._executed: list[OrderCommand] = []
def run(self, commands: list[OrderCommand]) -> bool:
print(f"\n[Processor] Starting transaction with {len(commands)} commands")
self._executed.clear()
for cmd in commands:
print(f"\n[Processor] Executing: {cmd.description}")
try:
cmd.execute()
self._executed.append(cmd)
except Exception as e:
print(f"\n[Processor] ⚠ Command failed: {e}")
print("[Processor] Rolling back all executed commands...")
self._rollback()
return False
print("\n[Processor] ✓ All commands succeeded")
return True
def _rollback(self) -> None:
for cmd in reversed(self._executed):
print(f" [Rollback] Undoing: {cmd.description}")
try:
cmd.undo()
except Exception as e:
print(f" [Rollback] ⚠ Undo failed for {cmd.description}: {e}")
self._executed.clear()
# ─── Client ────────────────────────────────────────────────────────
if __name__ == "__main__":
inventory = InventoryService()
payment = PaymentService()
shipping = ShippingService()
audit = AuditLog()
processor = TransactionalProcessor()
# ── Successful Order ──
print("=" * 55)
print("SCENARIO 1: Successful order")
print("=" * 55)
order1 = Order(customer_id="CUST-001", items=[
OrderItem("P001", 2, 29.99),
OrderItem("P002", 1, 49.99),
])
print(f"Order total: ${order1.total:.2f}")
commands = [
AuditCommand(ReserveInventoryCommand(order1, inventory), audit),
AuditCommand(ChargePaymentCommand(order1, payment), audit),
AuditCommand(CreateShipmentCommand(order1, shipping), audit),
]
success = processor.run(commands)
print(f"\nOrder status: {order1.status}")
print(f"Stock P001: {inventory.stock('P001')}, P002: {inventory.stock('P002')}")
# ── Failed Order (stock too low) ──
print("\n" + "=" * 55)
print("SCENARIO 2: Order fails — insufficient stock")
print("=" * 55)
order2 = Order(customer_id="CUST-002", items=[
OrderItem("P002", 1, 49.99),
OrderItem("P003", 99, 9.99), # Only 5 in stock — will fail
])
commands2 = [
AuditCommand(ReserveInventoryCommand(order2, inventory), audit),
AuditCommand(ChargePaymentCommand(order2, payment), audit),
AuditCommand(CreateShipmentCommand(order2, shipping), audit),
]
processor.run(commands2)
print(f"\nOrder status: {order2.status}")
print(f"Stock P002 after rollback: {inventory.stock('P002')} (should be restored)")
# ── Audit replay ──
audit.replay()
Real-World Use Cases
1. Text Editor Undo/Redo
Every action in a text editor — typing, deleting, formatting, pasting — is a Command. The editor's Invoker maintains a history stack. Ctrl+Z pops the last command and calls undo(); Ctrl+Y re-executes it.
class TypeTextCommand(Command):
def __init__(self, doc: Document, position: int, text: str):
self._doc = doc
self._position = position
self._text = text
def execute(self) -> None:
self._doc.insert(self._position, self._text)
def undo(self) -> None:
self._doc.delete(self._position, len(self._text))
2. Job Queues and Task Schedulers
Background processors (Celery, Sidekiq, RQ) serialize command objects to a queue. Workers deserialize and execute them. Failed commands can be retried because the command object carries all necessary context.
import json
@dataclass
class SendEmailCommand:
recipient: str
subject: str
body: str
def execute(self) -> None:
email_service.send(self.recipient, self.subject, self.body)
def serialize(self) -> str:
return json.dumps({"type": "SendEmail", "to": self.recipient,
"subject": self.subject, "body": self.body})
3. Database Transactions / Migration Scripts
Each migration step (create table, add column, insert seed data) is a Command with an up() (execute) and down() (undo). Database migration tools like Flyway and Alembic apply this pattern.
4. GUI Toolbars and Keyboard Shortcuts
A toolbar button knows nothing about what it does — it holds a Command reference. Pressing the button calls command.execute(). The same command object can be assigned to a keyboard shortcut, a context menu item, and a toolbar button simultaneously without any duplication.
5. Event Sourcing
In event-sourced systems, every state change is recorded as an immutable command (event). The current state of an entity is derived by replaying its entire command history from the beginning. This gives you complete audit trails and the ability to reconstruct historical state at any point in time.
class EventStore:
def append(self, command: Command) -> None:
serialized = command.serialize()
self._storage.write(serialized)
def replay_from(self, entity_id: str, target_version: int) -> EntityState:
state = EntityState()
for cmd in self._storage.read_events(entity_id, up_to=target_version):
cmd.execute_on(state)
return state
6. Network Request Retry
HTTP clients and API SDKs wrap requests as commands and retry failed ones automatically. The command carries the URL, method, headers, and body — everything needed to re-execute identically.
Language-Specific Mistakes and Anti-Patterns
❌ Mistake #1: Not Saving State Before execute() for Undo
# BAD: undo() has no idea what the previous state was — it resets to a hardcoded value
class SetBrightnessCommand(Command):
def execute(self):
self._light.set_brightness(self._level)
def undo(self):
self._light.set_brightness(100) # Wrong! Assumes previous value was always 100
# GOOD: Capture the state snapshot at the start of execute()
class SetBrightnessCommand(Command):
def execute(self):
self._prev_level = self._light.brightness # Snapshot before change
self._light.set_brightness(self._level)
def undo(self):
self._light.set_brightness(self._prev_level) # Restore exact snapshot
❌ Mistake #2: Using Lambdas as Commands Without Undo Support
# BAD: Lambda callbacks are Commands without undo — once the pattern requires undo,
# you have to rewrite all lambdas as full command classes
button.on_click = lambda: light.turn_on()
# How do you undo this? You can't without storing state somewhere.
# GOOD: Even simple commands should be classes when undo is a requirement
class LightOnCommand(Command):
def execute(self): self._light.turn_on()
def undo(self): self._light.turn_off()
# If undo is truly not needed and commands are fire-and-forget:
# lambdas or functools.partial are perfectly fine lightweight alternatives
from functools import partial
button.on_click = partial(light.turn_on)
❌ Mistake #3: Command Holds Too Much Logic (God Command)
# BAD: Command contains all the business logic — bypasses the Receiver entirely
class ProcessOrderCommand(Command):
def execute(self):
# Validation, DB queries, payment processing, email sending — all here!
if not self._order.customer_id:
raise ValueError("Missing customer")
db.save(self._order)
stripe.charge(self._order.total)
sendgrid.send_confirmation(self._order.customer_id)
# GOOD: Command delegates to receivers; it only coordinates
class ProcessOrderCommand(Command):
def execute(self):
self._validator.validate(self._order) # Receiver 1
self._txn_id = self._payment.charge(...) # Receiver 2
self._email.send_confirmation(...) # Receiver 3
Frequently Asked Questions
Q1: What is the difference between Command and Strategy?
Both patterns encapsulate behavior in an object, which is why they're often confused. The key difference is intent and lifecycle:
Strategy encapsulates an algorithm — a how. You swap strategies to change how an operation is performed (e.g., sort by name vs. sort by date). Strategies are interchangeable implementations of the same contract and are typically stateless.
Command encapsulates a request — a what, on what, with what arguments. Commands are typically stateful (they capture the receiver and parameters) and carry undo information. They are tied to a specific action at a specific moment.
A sorting widget uses Strategy (which sort algorithm to use). A "sort ascending" button click uses Command (that specific sort action at that specific moment, which can be undone).
Q2: When should I use Command vs. a simple callback / lambda?
Use a lambda/callback when:
- Execution is fire-and-forget (no undo needed)
- The action doesn't need to be stored, serialized, or logged
- You don't need a history
Use the full Command pattern when:
- You need undo/redo support
- Commands must be queued, scheduled, or deferred
- You want to log, audit, or replay operations
- You need macro commands (composing multiple actions atomically)
- Commands must be serialized and transmitted (job queues, event sourcing)
Q3: How do you implement undo for commands that have side effects like network calls?
For side-effectful operations, the undo command is a compensating action rather than a literal reversal:
class CreateUserCommand(Command):
def execute(self):
self._user_id = self._api.create_user(self._data) # POST /users
def undo(self):
if self._user_id:
self._api.delete_user(self._user_id) # DELETE /users/{id}
If the compensating action can also fail, you need saga-style compensation — each command logs its compensating action to a persistent store so recovery can proceed even after a crash.
Q4: What is a Macro Command and when should I use it?
A Macro Command (also called Composite Command) groups multiple commands into a single logical unit. execute() runs all sub-commands in order; undo() reverses them in reverse order. Use it when:
- Multiple operations must succeed or fail atomically
- You want to present a complex multi-step action as a single undoable unit to the user
- You're building "record a macro" functionality in an editor or automation tool
Q5: How does Command relate to event sourcing?
Event sourcing is Command at the architectural level. Every state mutation is represented as an immutable command (event) appended to a log. The current state is derived by replaying commands from the beginning. This gives you:
- Complete audit history
- The ability to reconstruct historical state at any point
- Easy replication (replay the log on another node)
- Time-travel debugging
The command object becomes the event, and execute() becomes "apply this event to the current state."
Q6: Can the same command object be executed multiple times?
Only if it is designed to be idempotent and re-entrant. In general, command objects should be used once per action — each user action creates a new command instance. If you execute the same instance twice, the second execute() will overwrite prevLevel (the undo snapshot) from the first execution, making the first undo incorrect.
# WRONG: Reusing a command instance
cmd = SetBrightnessCommand(light, 50)
cmd.execute() # prevLevel saved as 100
cmd.execute() # prevLevel overwritten as 50 — first undo is now broken!
# RIGHT: Create a new instance per action
invoker.execute(SetBrightnessCommand(light, 50)) # Fresh instance
invoker.execute(SetBrightnessCommand(light, 80)) # Another fresh instance
Q7: How do I limit the undo history size?
Use a bounded deque (double-ended queue) that drops the oldest entry when it exceeds the limit:
from collections import deque
class Invoker:
def __init__(self, max_history: int = 50):
self._history = deque(maxlen=max_history) # Auto-discards oldest
def execute(self, cmd: Command) -> None:
cmd.execute()
self._history.append(cmd) # Oldest dropped automatically when full
def undo(self) -> None:
if self._history:
self._history.pop().undo()
Q8: How do I serialize and deserialize commands for job queues?
Make each command serializable to a dictionary or JSON, and register all command types in a registry so deserialization can reconstruct the correct class:
import json
COMMAND_REGISTRY: dict[str, type] = {}
def register_command(cls):
COMMAND_REGISTRY[cls.__name__] = cls
return cls
@register_command
class SendEmailCommand(Command):
def __init__(self, to: str, subject: str, body: str):
self._to = to
self._subject = subject
self._body = body
def execute(self): email_service.send(self._to, self._subject, self._body)
def undo(): pass # Non-reversible
def to_dict(self) -> dict:
return {"type": "SendEmailCommand", "to": self._to,
"subject": self._subject, "body": self._body}
@classmethod
def from_dict(cls, data: dict) -> "SendEmailCommand":
return cls(data["to"], data["subject"], data["body"])
def deserialize(raw: str) -> Command:
data = json.loads(raw)
cls = COMMAND_REGISTRY[data["type"]]
return cls.from_dict(data)
Q9: How do I test a command in isolation?
Inject mock receivers and verify that:
execute()calls the correct receiver methods with the correct arguments.undo()reverses exactly whatexecute()did.undo()is a no-op if called beforeexecute().
from unittest.mock import Mock, call
def test_set_brightness_execute_and_undo():
light = Mock()
light.brightness = 80 # Initial state
light.is_on = True
cmd = SetBrightnessCommand(light, 30)
cmd.execute()
light.set_brightness.assert_called_once_with(30)
cmd.undo()
light.set_brightness.assert_called_with(80) # Restored to snapshot
light.turn_off.assert_not_called() # Was already on, stays on
def test_undo_before_execute_is_safe():
light = Mock()
light.brightness = 100
light.is_on = True
cmd = SetBrightnessCommand(light, 50)
cmd.undo() # Should not raise or call anything
light.set_brightness.assert_not_called()
Q10: What is the Null Object / No-Op Command and why is it useful?
A NoOpCommand implements the Command interface but does nothing in execute() and undo(). It is the Null Object pattern applied to commands. Use it to:
- Pre-fill invoker slots so you never need to check for
nullbefore callingexecute() - Represent "no action assigned yet" without special-casing it
- Simplify invoker logic — it can always call
execute()safely, even on unbound buttons
class NoOpCommand(Command):
def execute(self) -> None: pass
def undo(self) -> None: pass
def description(self) -> str: return "NoOp"
Key Takeaways
🎯 Core Concept: The Command Pattern encapsulates a request as a standalone object that holds all information needed to perform an action — the receiver, the method, and the arguments. This decouples the object that invokes the operation from the object that knows how to perform it, and enables queuing, logging, undoing, and composing actions.
🔑 Key Benefits:
- Undo/Redo: Commands snapshot state before executing and restore it on undo — the foundation of every undo stack
- Decoupling: Invokers (buttons, schedulers, APIs) know only the Command interface; they're completely independent of receivers
- Extensibility: New commands are added without touching the invoker or existing commands
- Composability: MacroCommands bundle multiple actions into a single atomic, undoable unit
- Auditability: Every executed command is an object — serialize it, log it, replay it
🌐 Language-Specific Highlights:
- Python: Snapshot receiver state at the start of
execute()(not at construction time); usefrozen=Falsedataclasses or simple instance variables for state; usedeque(maxlen=N)for bounded undo history - TypeScript: Guard
undo()with anexecutedboolean flag; useCommand<TRequest, TResponse>generics; make the entire chainasyncif any command is async - Java: Use
newper action (never singleton commands); useDequefor the history stack; clear the redo stack on every newexecute(); userecordfor immutable value objects in commands - JavaScript: Use arrow functions or explicit
.bind()to preservethis; use private class fields (#) for command state; avoid plain object literals when undo is needed - C#: Use
async Task(neverasync void) for async commands; catch exceptions in rollback loops so all undos are attempted; use primary constructors for concise command definitions - PHP: Store only the minimal undo state (transaction ID, not full object clones); always define a
Commandinterface for type safety - Go: Use pointer receivers for all handlers with state; protect the history slice with a
sync.Mutexfor concurrent invokers; designCommandwithUndo()from the start - Rust: Use
Box<dyn Command>for heterogeneous command histories; prefer arena/index patterns over references to avoid lifetime complexity; use closures for simple ad-hoc commands - Dart: Guard
undo()with an_executedflag; preferabstract classovermixinfor the Command base; useremoveLast()fromListfor the undo stack - Swift: Use
class(notstruct) for commands that carry mutable undo state; usepopLast()for the history stack; leverage protocols with default implementations - Kotlin: Never use
object(singleton) for stateful commands — useclass; leverageLambdaCommanddata class for simple cases; useArrayDeque.removeLastOrNull()for safe undo
📋 When to Use Command:
- You need multi-level undo/redo in an editor, drawing tool, or any interactive application
- Operations must be deferred, scheduled, or queued for later execution
- You want to log every action for audit trails, debugging, or event sourcing
- Multiple unrelated objects (buttons, hotkeys, menu items) should trigger the same action
- You need transactional behavior — if one step fails, roll back the rest
- You're building a job queue, task scheduler, or background processing system
⚠️ When NOT to Use Command:
- Simple fire-and-forget actions with no undo needed — a lambda or callback is lighter
- The action doesn't need to be stored, queued, or replayed — direct method calls are clearer
- Commands are so numerous and short-lived that the object overhead is a performance concern — consider pooling or functional approaches
- The receiver interface is large — you'd need one command class per method, which can explode the class count
🛠️ Best Practices Across Languages:
- Snapshot state at
execute()time, not construction time: The receiver's state at the moment you press the button is what you want to capture for undo — not the state when the command was created - Guard
undo()with anexecutedflag: Never allow undo to run if execute hasn't been called - One command instance per user action: Commands are value objects tied to a specific action — never reuse an instance for a second action
- Clear the redo stack on every new
execute(): New actions invalidate stale redo history - Add a NoOp for unbound slots: Eliminates null checks in the invoker
- Rollback loops should be exhaustive: Catch exceptions per command in rollback so one failing undo doesn't skip the rest
- Keep commands thin: Delegate real work to receivers; commands coordinate, receivers operate
- Bound undo history: Use a max-size deque to prevent unbounded memory growth
💡 Common Pitfalls:
- Not saving state for undo: Hardcoded "reset to default" in
undo()is wrong — always snapshot the actual previous state - Shared mutable command instances: Two executions overwrite each other's undo snapshot
- Undo before execute: Unguarded
undo()calls produce undefined behavior - God command: Commands that contain all business logic bypass the receiver and are untestable
- Missing redo stack clear: After a new action, stale redo commands corrupt state on re-execution
async voidin C#: Exceptions are unobservable — always useasync Task- Struct commands in Swift: Value semantics copy the undo state away from the history stack
🎁 Advanced Techniques:
- Event Sourcing: Store every command as an immutable event; replay the log to derive current state at any version
- Transactional Processor: Wrap multiple commands in a transaction; roll back all on failure
- Command Bus: A message bus where components publish commands and subscribers execute them — fully decouples producers from consumers
- Saga Pattern: For distributed transactions, each service executes a command and registers a compensating command; a saga coordinator rolls back on failure across service boundaries
- Command Compression: When the same command type is applied repeatedly (e.g., typing), merge consecutive commands into one (e.g.,
TypeTextCommand("hell") + TypeTextCommand("o")→TypeTextCommand("hello")) to reduce history size - Lazy Command: Command stores only parameters at creation; acquires the receiver lazily at execute time — useful in dependency injection scenarios
The Command Pattern turns actions into data. Once an action is an object, everything becomes possible: undo it, queue it, log it, serialize it, compose it, replay it. It is the foundation of every undo stack, every job queue, every event-sourced system, and every transactional workflow in modern software.
Want to dive deeper into design patterns? Check out our comprehensive guides on:
- Chain of Responsibility Pattern
- Observer Pattern
- Strategy Pattern
- Memento Pattern
- Visitor Pattern Each guide includes examples in all 11 programming languages with language-specific best practices.