Behavioral Pattern

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?

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:

  1. Undo / Redo: Commands store everything needed to reverse an operation — enabling multi-level undo in editors, IDEs, and creative tools
  2. Deferred Execution: Commands can be queued and executed later — at the right time, by the right thread, under the right conditions
  3. Macro Commands: Composite commands group multiple operations into a single atomic action
  4. Logging and Audit Trails: Every executed command is an object that can be persisted, serialized, and replayed
  5. Transactional Behavior: If a compound operation fails halfway, stored commands can be rolled back in reverse order
  6. Parameterization: Buttons, menu items, and hotkeys can all be wired to different command objects without changing the UI code
  7. 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:

PatternEncapsulatesRelationship to ReceiverKey CapabilityUse Case
CommandA request as an objectHolds a reference to receiverUndo, queuing, logging, macroEditor actions, job queues, transactions
StrategyAn algorithmIS the algorithmSwappable behaviorSorting, pricing, compression
Chain of ResponsibilityA handler decisionPasses to next handlerDynamic routing of requestsMiddleware, escalation
ObserverA subscriptionNotified on eventBroadcast to manyEvent systems, reactive UI
Template MethodAn algorithm skeletonDefined in base classStep customizationFrameworks, 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:

  1. Command Interface: Declares the execute() method and, optionally, an undo() method. Every command in the system implements this interface.
  2. Concrete Command: Implements execute() and undo(). Holds a reference to the Receiver and the parameters needed to perform the action. Delegates actual work to the receiver.
  3. Receiver: The object that knows how to perform the actual operation. Contains the real business logic. The command calls methods on it.
  4. 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.
  5. 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 of execute().
  • 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:

  1. execute() calls the correct receiver methods with the correct arguments.
  2. undo() reverses exactly what execute() did.
  3. undo() is a no-op if called before execute().
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 null before calling execute()
  • 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); use frozen=False dataclasses or simple instance variables for state; use deque(maxlen=N) for bounded undo history
  • TypeScript: Guard undo() with an executed boolean flag; use Command<TRequest, TResponse> generics; make the entire chain async if any command is async
  • Java: Use new per action (never singleton commands); use Deque for the history stack; clear the redo stack on every new execute(); use record for immutable value objects in commands
  • JavaScript: Use arrow functions or explicit .bind() to preserve this; use private class fields (#) for command state; avoid plain object literals when undo is needed
  • C#: Use async Task (never async 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 Command interface for type safety
  • Go: Use pointer receivers for all handlers with state; protect the history slice with a sync.Mutex for concurrent invokers; design Command with Undo() 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 _executed flag; prefer abstract class over mixin for the Command base; use removeLast() from List for the undo stack
  • Swift: Use class (not struct) for commands that carry mutable undo state; use popLast() for the history stack; leverage protocols with default implementations
  • Kotlin: Never use object (singleton) for stateful commands — use class; leverage LambdaCommand data class for simple cases; use ArrayDeque.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:

  1. 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
  2. Guard undo() with an executed flag: Never allow undo to run if execute hasn't been called
  3. One command instance per user action: Commands are value objects tied to a specific action — never reuse an instance for a second action
  4. Clear the redo stack on every new execute(): New actions invalidate stale redo history
  5. Add a NoOp for unbound slots: Eliminates null checks in the invoker
  6. Rollback loops should be exhaustive: Catch exceptions per command in rollback so one failing undo doesn't skip the rest
  7. Keep commands thin: Delegate real work to receivers; commands coordinate, receivers operate
  8. 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 void in C#: Exceptions are unobservable — always use async 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: