Chapter 05 · Article 24 of 55

Command Pattern

Intent: Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.

Article outline15 sections on this page

Overview

Intent: Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.

The Command pattern turns a request into a stand-alone object that contains all information about the request - the method to call, the method's arguments, and the object that owns the method. This transformation lets you pass requests as method arguments, delay or queue a request's execution, and support undoable operations.

Also known as: Action, Transaction

Type: Behavioural Design Pattern

Complexity: **


Problem It Solves

In many systems, the object that invokes an operation is tightly coupled to the object that knows how to perform it. This coupling creates several problems:

  1. Invoker-Receiver Coupling: A GUI button directly calls business logic, making it impossible to reuse the button for different operations without modifying its code.

  2. No Undo/Redo Support: Without a history of operations, reversing actions requires ad-hoc state snapshots or manual bookkeeping.

  3. No Command Queuing: Operations that need to be scheduled, batched, or executed remotely cannot be easily serialized or deferred.

  4. No Macro Commands: Composing multiple operations into a single logical unit (e.g., "format document" = bold title + indent paragraphs + add footer) requires complex orchestration code.

  5. No Logging/Auditing: Without reified operations, you cannot replay, audit, or persist the sequence of actions performed on a system.

The Command pattern solves all of these by introducing an abstraction layer - the command object - between the invoker and the receiver.


When to Use / When NOT to Use

When to UseWhen NOT to Use
You need to parameterize objects with operationsSimple direct method calls with no need for decoupling
You need undo/redo functionalityOperations that are trivially reversible via state reset
You need to queue, schedule, or log operationsOverhead of command objects outweighs benefits (simple CRUD)
You need to support macro (composite) commandsSystem has only one invoker calling one receiver
You need to implement transactional behaviourReal-time systems where object creation latency matters
You want to decouple UI from business logicWhen callbacks or lambdas suffice and no history is needed
You need remote execution or serialization of requestsStateless request-response with no side effects to track

Key Concepts & Theory

The Four Roles

RoleResponsibility
CommandDeclares the interface (execute, undo) for performing an operation
ConcreteCommandImplements execute by invoking operations on the Receiver; stores state for undo
ReceiverThe object that actually performs the work (business logic)
InvokerAsks the command to carry out the request; holds command references
ClientCreates ConcreteCommand objects and sets their receivers

Command as a First-Class Object

Once a request is encapsulated as an object, it gains all the properties of objects:

  • It can be stored in data structures (stacks, queues, lists)
  • It can be serialized to disk or transmitted over a network
  • It can be parameterized - the same invoker works with any command
  • It can be composed - multiple commands form a macro command

The Undo Stack

The invoker maintains a history stack of executed commands. Each command stores enough state to reverse its effect:

Execute: push command onto history stack
Undo:    pop from history stack, call command.undo()
Redo:    pop from redo stack, call command.execute(), push onto history

Key invariant: After undo, if a new command is executed, the redo stack is cleared (the "branching" timeline is discarded).

Macro Commands (Composite Commands)

A MacroCommand holds a list of sub-commands and executes them sequentially. Undo reverses them in reverse order. This is the Command pattern combined with the Composite pattern.


ASCII Class Diagram

┌─────────────────────────────────────────────────────────────────────┐
│                            CLIENT                                    │
│  (creates commands, assigns receivers, configures invoker)           │
└──────────────────────────────┬──────────────────────────────────────┘
                               │ creates
                               ▼
┌──────────────┐       ┌──────────────────┐       ┌──────────────────┐
│   INVOKER    │       │   <<interface>>   │       │    RECEIVER      │
│              │       │     Command       │       │                  │
│ - history[]  │       │──────────────────│       │ + action()       │
│              │ holds │ + execute()       │       │ + undoAction()   │
│ + execute-   │──────▶│ + undo()          │       │                  │
│   Command()  │       └──────────────────┘       └────────▲─────────┘
│ + undo()     │               △                           │
│ + redo()     │               │ implements                │ calls
└──────────────┘               │                           │
                    ┌──────────┴──────────┐                │
                    │                     │                │
          ┌─────────────────┐   ┌─────────────────┐       │
          │ ConcreteCommandA│   │ ConcreteCommandB│       │
          │─────────────────│   │─────────────────│       │
          │ - receiver      │───┘ - receiver      │───────┘
          │ - prevState     │     - prevState     │
          │─────────────────│     │─────────────────│
          │ + execute()     │     │ + execute()     │
          │ + undo()        │     │ + undo()        │
          └─────────────────┘     └─────────────────┘

Pseudocode Implementation

Example 1: Text Editor with Undo/Redo

// Command Interface
interface Command {
    execute()
    undo()
}

// Receiver
class TextEditor {
    content = ""
    selectionStart = 0
    selectionEnd = 0

    insert(text, position):
        content = content[0..position] + text + content[position..]

    delete(start, end):
        removed = content[start..end]
        content = content[0..start] + content[end..]
        return removed

    applyBold(start, end):
        // wraps selection in **bold** markers
        insert("**", end)
        insert("**", start)

    removeBold(start, end):
        // removes bold markers around selection
        delete(end, end+2)
        delete(start, start+2)
}

// Concrete Commands
class TypeCommand implements Command {
    editor: TextEditor
    text: String
    position: Int

    constructor(editor, text, position):
        this.editor = editor
        this.text = text
        this.position = position

    execute():
        editor.insert(text, position)

    undo():
        editor.delete(position, position + text.length)
}

class DeleteCommand implements Command {
    editor: TextEditor
    start: Int
    end: Int
    deletedText: String  // saved for undo

    constructor(editor, start, end):
        this.editor = editor
        this.start = start
        this.end = end

    execute():
        deletedText = editor.delete(start, end)

    undo():
        editor.insert(deletedText, start)
}

class BoldCommand implements Command {
    editor: TextEditor
    start: Int
    end: Int

    constructor(editor, start, end):
        this.editor = editor
        this.start = start
        this.end = end

    execute():
        editor.applyBold(start, end)

    undo():
        editor.removeBold(start, end)
}

// Invoker
class EditorInvoker {
    historyStack: Stack<Command> = []
    redoStack: Stack<Command> = []

    executeCommand(cmd: Command):
        cmd.execute()
        historyStack.push(cmd)
        redoStack.clear()  // new action invalidates redo

    undo():
        if historyStack.isEmpty(): return
        cmd = historyStack.pop()
        cmd.undo()
        redoStack.push(cmd)

    redo():
        if redoStack.isEmpty(): return
        cmd = redoStack.pop()
        cmd.execute()
        historyStack.push(cmd)
}

// Client Usage
editor = new TextEditor()
invoker = new EditorInvoker()

invoker.executeCommand(new TypeCommand(editor, "Hello World", 0))
// content: "Hello World"

invoker.executeCommand(new BoldCommand(editor, 0, 5))
// content: "**Hello** World"

invoker.undo()
// content: "Hello World"

invoker.redo()
// content: "**Hello** World"

Example 2: Smart Home Remote Control

// Receivers
class Light {
    isOn = false
    turnOn():  isOn = true
    turnOff(): isOn = false
}

class CeilingFan {
    speed = 0  // 0=off, 1=low, 2=medium, 3=high
    setSpeed(s): speed = s
    getSpeed(): return speed
}

// Concrete Commands
class LightOnCommand implements Command {
    light: Light

    execute(): light.turnOn()
    undo():    light.turnOff()
}

class LightOffCommand implements Command {
    light: Light

    execute(): light.turnOff()
    undo():    light.turnOn()
}

class FanSpeedCommand implements Command {
    fan: CeilingFan
    newSpeed: Int
    prevSpeed: Int

    constructor(fan, newSpeed):
        this.fan = fan
        this.newSpeed = newSpeed

    execute():
        prevSpeed = fan.getSpeed()
        fan.setSpeed(newSpeed)

    undo():
        fan.setSpeed(prevSpeed)
}

// Macro Command
class MacroCommand implements Command {
    commands: List<Command>

    constructor(commands):
        this.commands = commands

    execute():
        for cmd in commands: cmd.execute()

    undo():
        for cmd in commands.reversed(): cmd.undo()
}

// Invoker
class RemoteControl {
    slots: Map<Int, Command>
    history: Stack<Command> = []

    setCommand(slot, cmd):
        slots[slot] = cmd

    pressButton(slot):
        cmd = slots[slot]
        cmd.execute()
        history.push(cmd)

    pressUndo():
        if history.isEmpty(): return
        cmd = history.pop()
        cmd.undo()
}

// Client
light = new Light()
fan = new CeilingFan()
remote = new RemoteControl()

remote.setCommand(1, new LightOnCommand(light))
remote.setCommand(2, new FanSpeedCommand(fan, 3))
remote.setCommand(3, new MacroCommand([
    new LightOnCommand(light),
    new FanSpeedCommand(fan, 2)
]))

remote.pressButton(1)  // light on
remote.pressButton(2)  // fan high
remote.pressUndo()     // fan back to 0

Command Queue and Scheduling

Commands, being self-contained objects, are naturally suited for queuing, logging, and replay.

class CommandQueue {
    queue: Queue<Command> = []
    log: List<CommandLogEntry> = []

    enqueue(cmd: Command):
        queue.add(cmd)

    processNext():
        if queue.isEmpty(): return
        cmd = queue.dequeue()
        cmd.execute()
        log.append(CommandLogEntry(cmd, timestamp=now()))

    processAll():
        while not queue.isEmpty():
            processNext()

    // Replay from log (e.g., after crash recovery)
    replay(fromTimestamp):
        for entry in log:
            if entry.timestamp >= fromTimestamp:
                entry.command.execute()

    // Scheduled execution
    schedule(cmd: Command, executeAt: Timestamp):
        scheduler.addJob(executeAt, () -> {
            cmd.execute()
            log.append(CommandLogEntry(cmd, timestamp=executeAt))
        })
}

// Serialization for persistence / network transfer
class SerializableCommand {
    serialize(cmd: Command) -> JSON:
        return { type: cmd.className, params: cmd.getParams() }

    deserialize(json: JSON) -> Command:
        return CommandFactory.create(json.type, json.params)
}

Use cases for command queuing:

  • Job queues: Background workers pull commands from a queue and execute them
  • Crash recovery: Replay logged commands from the last checkpoint
  • Distributed systems: Serialize commands and send over the network
  • Rate limiting: Buffer commands and process at controlled intervals
  • Batch processing: Accumulate commands and execute in a single transaction

Real-World Examples

DomainExampleCommandInvokerReceiver
GUI ApplicationsMenu items, toolbar buttons, keyboard shortcutsMenuItem actionMenu/ButtonApplication logic
Transaction SystemsBanking transfers, order processingTransferFundsCommandTransactionManagerAccountService
Task SchedulersCron jobs, delayed executionScheduledTaskSchedulerWorker service
Database MigrationsSchema changes with rollbackMigrationStep (up/down)MigrationRunnerDatabase
Game Input ReplayRecording and replaying player actionsInputCommand (move, shoot)ReplayEngineGameState
Text EditorsUndo/redo in VS Code, VimEditActionUndoManagerDocument buffer
CI/CD PipelinesBuild steps, deploy stepsPipelineStepPipelineRunnerBuild environment
Network RequestsRetry-able HTTP operationsRequestCommandRetryHandlerHTTPClient

Command vs Strategy vs Observer

AspectCommandStrategyObserver
IntentEncapsulate a request as an objectDefine a family of interchangeable algorithmsNotify dependents of state changes
RelationshipInvoker → Command → ReceiverContext → StrategySubject → Observer
Decouples"What to do" from "when to do it""How to do it" from "who uses it""State change" from "reaction to change"
Stores stateYes (for undo)No (stateless algorithms)No (observers maintain own state)
ComposableYes (macro commands)No (one strategy at a time)Yes (multiple observers)
Typical useUndo, queuing, loggingAlgorithm selection at runtimeEvent systems, pub/sub
LifetimeCreated, executed, possibly storedSet once, swapped occasionallyRegistered, notified, unregistered
DirectionOne-to-one (invoker to receiver)One-to-one (context to algorithm)One-to-many (subject to observers)

Key distinction: A Command says "do this specific thing (and remember how to undo it)". A Strategy says "do this kind of thing, but I don't care how". An Observer says "something happened, react however you want".


Advantages & Disadvantages

AdvantagesDisadvantages
Decouples invoker from receiver (SRP)Increases number of classes in the system
Easy to add new commands (OCP)Can be overkill for simple operations
Supports undo/redo naturallyMemory overhead from storing command history
Enables command queuing and schedulingComplexity in managing undo for complex state
Commands are composable (macro commands)Serialization of commands with complex state is non-trivial
Supports logging, auditing, and replayDebugging can be harder (indirection)
Enables transactional behaviourPotential for stale receiver references
Facilitates testing (commands are testable units)Undo correctness is hard to guarantee in concurrent systems

Constraints & Edge Cases

Command Serialization

When commands need to be persisted or sent over a network, they must be serializable. This is challenging when commands hold references to complex objects (receivers). Solutions:

  • Store receiver identifiers instead of references; resolve at execution time
  • Use the Memento pattern alongside Command for state snapshots
  • Implement a CommandFactory that reconstructs commands from serialized data

Large Undo History Memory

An unbounded history stack can consume significant memory, especially for commands that store large state snapshots (e.g., image editor operations). Mitigations:

  • Limit history depth - discard oldest commands beyond N entries
  • Checkpoint strategy - periodically save full state, only keep commands since last checkpoint
  • Delta storage - store only diffs rather than full state copies
  • Compress - serialize and compress old command state

Compensating Commands (When Undo Is Impossible)

Some operations cannot be truly reversed (e.g., sending an email, charging a credit card, deleting data from an external system). In these cases:

  • Use compensating commands - a new forward action that logically reverses the effect (e.g., refund instead of "un-charge")
  • Implement saga pattern - a sequence of commands with corresponding compensations
  • Mark commands as non-undoable and prevent undo past that point

Idempotency

Commands that may be retried (network failures, queue redelivery) must be idempotent:

  • Assign unique IDs to commands; check for duplicate execution
  • Design execute() to produce the same result regardless of how many times it runs
  • Use conditional writes (e.g., "set X to 5" is idempotent; "increment X" is not)

Concurrency Concerns

  • Commands modifying shared state need synchronization
  • Undo in multi-user systems requires conflict resolution (operational transformation or CRDTs)
  • Command queues need thread-safe implementations

Interview Follow-ups

Q1: How would you implement undo in a collaborative text editor where multiple users edit simultaneously?

A: In collaborative editors, simple undo stacks break because undoing your action may conflict with others' edits that happened after. The solution is Operational Transformation (OT) or CRDTs. Each command is transformed against concurrent operations before being applied or undone. The undo doesn't simply reverse the original command - it computes a new command that reverses the effect of the original in the current document state. Google Docs uses OT; Figma uses CRDTs.

Q2: How does the Command pattern relate to Event Sourcing?

A: Event Sourcing is essentially the Command pattern applied at the architectural level. Instead of storing current state, you store the sequence of commands (events) that produced that state. Current state is derived by replaying all events. This gives you: complete audit trail, ability to rebuild state at any point in time, natural undo (replay up to N-1), and temporal queries. The key difference: in Event Sourcing, events are facts that happened (past tense, immutable), while commands are requests to do something (may be rejected).

Q3: How would you handle a command that partially executes before failing?

A: This is the transaction problem. Options: (1) All-or-nothing - validate preconditions before execute, use transactions to ensure atomicity. (2) Compensating actions - if step 3 of 5 fails, run compensations for steps 1 and 2 in reverse order (saga pattern). (3) Two-phase commit - prepare all sub-operations first, then commit all or abort all. (4) Checkpoint and rollback - save state before execution, restore on failure. The choice depends on whether operations are local (use transactions) or distributed (use sagas).

Hint-Only Questions

H1: Design a command-based plugin system where third-party developers can register custom commands.

Hints:

  • Think about a CommandRegistry with register(name, factory) and execute(name, params)
  • Consider sandboxing - how do you prevent malicious commands from accessing unauthorized resources?
  • Think about command validation, versioning, and capability-based permissions

H2: How would you implement a "smart" undo that groups related commands (e.g., typing "hello" is one undo step, not five)?

Hints:

  • Consider time-based grouping - commands within 500ms of each other form a batch
  • Think about command coalescing - consecutive TypeCommands merge into one
  • Look at how the Memento pattern could snapshot state at "pause points" rather than every keystroke

Counter Questions to Ask Interviewer

  1. "What's the expected volume of commands?" - Determines whether you need a bounded history, persistent queue, or in-memory stack.

  2. "Do commands need to survive process restarts?" - Drives decisions about serialization, event sourcing, and persistence strategy.

  3. "Is undo required? If so, for how many levels?" - Unbounded undo has very different memory and complexity implications than "undo last 10 actions."

  4. "Are there commands that cannot be undone?" - Determines whether you need compensating commands, saga pattern, or confirmation dialogs.

  5. "Is this a single-user or multi-user system?" - Multi-user undo requires conflict resolution (OT/CRDTs), which fundamentally changes the design.

  6. "Do commands need to be composable into macros?" - Determines whether you need the Composite pattern integration.

  7. "What are the failure modes?" - Partial execution, network failures, and timeouts each require different strategies (sagas, retries, idempotency keys).


References & Whitepapers

  1. Gang of Four (GoF) - Design Patterns: Elements of Reusable Object-Oriented Software (1994), Chapter 5: Behavioral Patterns - Command.

  2. CQRS (Command Query Responsibility Segregation) - Greg Young (2010). Separates read and write models; the "C" in CQRS is directly the Command pattern applied at the architectural level. Commands mutate state; queries read it.

  3. Event Sourcing - Martin Fowler. Stores state as a sequence of events (commands that were executed). Naturally supports replay, audit, and temporal queries. Closely related to command logging.

  4. Saga Pattern - Hector Garcia-Molina & Kenneth Salem, "Sagas" (1987). Long-lived transactions as sequences of commands with compensating actions - directly builds on the Command pattern.

  5. Head First Design Patterns - Freeman & Robson, Chapter 6: The Command Pattern - excellent practical examples with remote control analogy.

  6. Martin Fowler - "Command" pattern entry in Patterns of Enterprise Application Architecture (PoEAA).

  7. Udi Dahan - "Clarified CQRS" - explains the relationship between commands, events, and domain models in distributed systems.