Command Pattern: Behavioral Design Guide

Updated 2026-03-11

TL;DR

The Command Pattern encapsulates a request as an object, allowing you to parameterize clients with different requests, queue operations, log requests, and support undo/redo functionality. It decouples the object that invokes the operation from the one that knows how to perform it.

Prerequisites: Understanding of classes and objects, interfaces/abstract classes, basic OOP principles (encapsulation, polymorphism), and familiarity with method calls and callbacks.

After this topic: Implement the Command Pattern to decouple request senders from receivers, create undo/redo functionality, build macro commands that execute multiple operations, and design systems where operations can be queued, logged, or scheduled for later execution.

Core Concept

What is the Command Pattern?

The Command Pattern is a behavioral design pattern that turns a request into a stand-alone object containing all information about the request. This transformation lets you pass requests as method arguments, delay or queue a request’s execution, and support undoable operations.

Core Components

The pattern consists of four key players:

  1. Command Interface: Declares an execute() method (and optionally undo()).
  2. Concrete Command: Implements the Command interface and defines the binding between a Receiver and an action.
  3. Receiver: The object that performs the actual work when the command’s execute() method is called.
  4. Invoker: Asks the command to carry out the request. It doesn’t know what the command does or who the receiver is.
  5. Client: Creates concrete command objects and sets their receivers.

Why Use the Command Pattern?

The Command Pattern solves several important problems:

Decoupling: The invoker doesn’t need to know anything about the receiver or the operation being performed. It just calls execute() on a command object.

Undo/Redo: By storing executed commands in a history stack, you can easily implement undo by calling an undo() method that reverses the operation.

Queuing and Scheduling: Commands can be stored in queues and executed later, enabling task scheduling, job queues, and transactional systems.

Macro Commands: You can create composite commands that execute multiple commands in sequence, useful for scripting and batch operations.

Logging and Auditing: Since commands are objects, you can log them for audit trails or crash recovery.

When to Use It

Consider the Command Pattern when you need to:

  • Parameterize objects with operations (like GUI buttons that perform different actions)
  • Queue operations for later execution
  • Support undo/redo functionality
  • Log changes so you can reapply them after a system crash
  • Structure a system around high-level operations built on primitive operations

Visual Guide

Command Pattern Structure

classDiagram
    class Command {
        <<interface>>
        +execute()
        +undo()
    }
    class ConcreteCommand {
        -receiver: Receiver
        -state
        +execute()
        +undo()
    }
    class Receiver {
        +action()
    }
    class Invoker {
        -command: Command
        +setCommand(cmd)
        +executeCommand()
    }
    class Client
    
    Command <|.. ConcreteCommand
    ConcreteCommand --> Receiver
    Invoker --> Command
    Client --> ConcreteCommand
    Client --> Receiver
    Client --> Invoker

The Command Pattern decouples the invoker from the receiver through a command interface. The client creates concrete commands and wires them to receivers.

Command Pattern Flow

sequenceDiagram
    participant Client
    participant Invoker
    participant Command
    participant Receiver
    
    Client->>Command: create(receiver)
    Client->>Invoker: setCommand(command)
    Client->>Invoker: executeCommand()
    Invoker->>Command: execute()
    Command->>Receiver: action()
    Receiver-->>Command: result
    Command-->>Invoker: done

Execution flow showing how the client sets up the command, the invoker triggers it, and the command delegates to the receiver.

Examples

Example 1: Smart Home Light Control with Undo

Let’s build a smart home system where we can control lights and undo operations.

from abc import ABC, abstractmethod

# Receiver: The object that knows how to perform the actual work
class Light:
    def __init__(self, location):
        self.location = location
        self.is_on = False
        self.brightness = 0
    
    def turn_on(self):
        self.is_on = True
        self.brightness = 100
        print(f"{self.location} light is ON at {self.brightness}% brightness")
    
    def turn_off(self):
        self.is_on = False
        self.brightness = 0
        print(f"{self.location} light is OFF")
    
    def dim(self, level):
        if self.is_on:
            self.brightness = level
            print(f"{self.location} light dimmed to {level}%")
        else:
            print(f"{self.location} light is off, cannot dim")

# Command Interface
class Command(ABC):
    @abstractmethod
    def execute(self):
        pass
    
    @abstractmethod
    def undo(self):
        pass

# Concrete Commands
class LightOnCommand(Command):
    def __init__(self, light):
        self.light = light
        self.previous_state = None
    
    def execute(self):
        self.previous_state = self.light.is_on
        self.light.turn_on()
    
    def undo(self):
        if not self.previous_state:
            self.light.turn_off()

class LightOffCommand(Command):
    def __init__(self, light):
        self.light = light
        self.previous_state = None
    
    def execute(self):
        self.previous_state = self.light.is_on
        self.light.turn_off()
    
    def undo(self):
        if self.previous_state:
            self.light.turn_on()

class DimLightCommand(Command):
    def __init__(self, light, level):
        self.light = light
        self.level = level
        self.previous_brightness = None
    
    def execute(self):
        self.previous_brightness = self.light.brightness
        self.light.dim(self.level)
    
    def undo(self):
        self.light.dim(self.previous_brightness)

# Invoker: Remote control that can execute commands and undo them
class RemoteControl:
    def __init__(self):
        self.history = []
    
    def execute_command(self, command):
        command.execute()
        self.history.append(command)
    
    def undo_last(self):
        if self.history:
            command = self.history.pop()
            command.undo()
        else:
            print("Nothing to undo")

# Client code
if __name__ == "__main__":
    # Create receivers
    living_room_light = Light("Living Room")
    bedroom_light = Light("Bedroom")
    
    # Create invoker
    remote = RemoteControl()
    
    # Create and execute commands
    print("=== Turning on lights ===")
    remote.execute_command(LightOnCommand(living_room_light))
    remote.execute_command(LightOnCommand(bedroom_light))
    
    print("\n=== Dimming living room ===")
    remote.execute_command(DimLightCommand(living_room_light, 50))
    
    print("\n=== Undo last operation ===")
    remote.undo_last()  # Undo dim
    
    print("\n=== Undo again ===")
    remote.undo_last()  # Undo bedroom light on
    
    print("\n=== Turn off living room ===")
    remote.execute_command(LightOffCommand(living_room_light))

Expected Output:

=== Turning on lights ===
Living Room light is ON at 100% brightness
Bedroom light is ON at 100% brightness

=== Dimming living room ===
Living Room light dimmed to 50%

=== Undo last operation ===
Living Room light dimmed to 100%

=== Undo again ===
Bedroom light is OFF

=== Turn off living room ===
Living Room light is OFF

Try it yourself: Add a MacroCommand class that can execute multiple commands at once (e.g., “Movie Mode” that dims lights and closes blinds).


Example 2: Text Editor with Undo/Redo

A practical example showing how commands enable undo/redo in a text editor.

from abc import ABC, abstractmethod
from typing import List

# Receiver: The document being edited
class Document:
    def __init__(self):
        self.content = ""
    
    def insert(self, position, text):
        self.content = self.content[:position] + text + self.content[position:]
        print(f"Inserted '{text}' at position {position}")
        print(f"Content: '{self.content}'")
    
    def delete(self, position, length):
        deleted = self.content[position:position + length]
        self.content = self.content[:position] + self.content[position + length:]
        print(f"Deleted '{deleted}' from position {position}")
        print(f"Content: '{self.content}'")
        return deleted
    
    def get_content(self):
        return self.content

# Command Interface
class EditCommand(ABC):
    @abstractmethod
    def execute(self):
        pass
    
    @abstractmethod
    def undo(self):
        pass

# Concrete Commands
class InsertCommand(EditCommand):
    def __init__(self, document, position, text):
        self.document = document
        self.position = position
        self.text = text
    
    def execute(self):
        self.document.insert(self.position, self.text)
    
    def undo(self):
        self.document.delete(self.position, len(self.text))

class DeleteCommand(EditCommand):
    def __init__(self, document, position, length):
        self.document = document
        self.position = position
        self.length = length
        self.deleted_text = None
    
    def execute(self):
        self.deleted_text = self.document.delete(self.position, self.length)
    
    def undo(self):
        if self.deleted_text:
            self.document.insert(self.position, self.deleted_text)

# Invoker: Editor with undo/redo stacks
class TextEditor:
    def __init__(self, document):
        self.document = document
        self.undo_stack: List[EditCommand] = []
        self.redo_stack: List[EditCommand] = []
    
    def execute(self, command):
        command.execute()
        self.undo_stack.append(command)
        # Clear redo stack when new command is executed
        self.redo_stack.clear()
    
    def undo(self):
        if self.undo_stack:
            command = self.undo_stack.pop()
            command.undo()
            self.redo_stack.append(command)
            print("Undo completed")
        else:
            print("Nothing to undo")
    
    def redo(self):
        if self.redo_stack:
            command = self.redo_stack.pop()
            command.execute()
            self.undo_stack.append(command)
            print("Redo completed")
        else:
            print("Nothing to redo")

# Client code
if __name__ == "__main__":
    doc = Document()
    editor = TextEditor(doc)
    
    print("=== Initial edits ===")
    editor.execute(InsertCommand(doc, 0, "Hello"))
    editor.execute(InsertCommand(doc, 5, " World"))
    editor.execute(InsertCommand(doc, 11, "!"))
    
    print("\n=== Undo twice ===")
    editor.undo()
    editor.undo()
    
    print("\n=== Redo once ===")
    editor.redo()
    
    print("\n=== New edit (clears redo stack) ===")
    editor.execute(DeleteCommand(doc, 5, 6))  # Delete " World"
    
    print("\n=== Try to redo (should fail) ===")
    editor.redo()
    
    print("\n=== Undo delete ===")
    editor.undo()

Expected Output:

=== Initial edits ===
Inserted 'Hello' at position 0
Content: 'Hello'
Inserted ' World' at position 5
Content: 'Hello World'
Inserted '!' at position 11
Content: 'Hello World!'

=== Undo twice ===
Deleted '!' from position 11
Content: 'Hello World'
Undo completed
Deleted ' World' from position 5
Content: 'Hello'
Undo completed

=== Redo once ===
Inserted ' World' at position 5
Content: 'Hello World'
Redo completed

=== New edit (clears redo stack) ===
Deleted ' World' from position 5
Content: 'Hello'

=== Try to redo (should fail) ===
Nothing to redo

=== Undo delete ===
Inserted ' World' at position 5
Content: 'Hello World'
Undo completed

Try it yourself: Implement a ReplaceCommand that combines delete and insert operations, ensuring both are undone together.


Java/C++ Notes

Java: Use interfaces for the Command abstraction. The pattern is identical in structure.

public interface Command {
    void execute();
    void undo();
}

public class LightOnCommand implements Command {
    private Light light;
    private boolean previousState;
    
    public LightOnCommand(Light light) {
        this.light = light;
    }
    
    public void execute() {
        previousState = light.isOn();
        light.turnOn();
    }
    
    public void undo() {
        if (!previousState) {
            light.turnOff();
        }
    }
}

C++: Use abstract base classes with virtual methods. Remember to use smart pointers for memory management.

class Command {
public:
    virtual ~Command() = default;
    virtual void execute() = 0;
    virtual void undo() = 0;
};

class LightOnCommand : public Command {
private:
    Light* light;
    bool previousState;
public:
    LightOnCommand(Light* l) : light(l), previousState(false) {}
    
    void execute() override {
        previousState = light->isOn();
        light->turnOn();
    }
    
    void undo() override {
        if (!previousState) {
            light->turnOff();
        }
    }
};

Common Mistakes

1. Not Storing Enough State for Undo

Mistake: Forgetting to save the previous state before executing a command, making undo impossible or incorrect.

# WRONG: No state saved
class BadDimCommand(Command):
    def __init__(self, light, level):
        self.light = light
        self.level = level
        # Missing: self.previous_brightness
    
    def execute(self):
        self.light.dim(self.level)  # Lost previous brightness!
    
    def undo(self):
        # What value to restore? We don't know!
        pass

Fix: Always capture the state you need to reverse the operation before executing it.

# CORRECT: State saved
class GoodDimCommand(Command):
    def __init__(self, light, level):
        self.light = light
        self.level = level
        self.previous_brightness = None
    
    def execute(self):
        self.previous_brightness = self.light.brightness  # Save state
        self.light.dim(self.level)
    
    def undo(self):
        self.light.dim(self.previous_brightness)  # Restore state

2. Mixing Business Logic with Command Logic

Mistake: Putting business logic directly in the command instead of delegating to the receiver.

# WRONG: Command contains business logic
class BadTransferCommand(Command):
    def __init__(self, from_account, to_account, amount):
        self.from_account = from_account
        self.to_account = to_account
        self.amount = amount
    
    def execute(self):
        # Business logic in command - hard to reuse and test
        if self.from_account.balance >= self.amount:
            self.from_account.balance -= self.amount
            self.to_account.balance += self.amount
        else:
            raise ValueError("Insufficient funds")

Fix: Commands should be thin wrappers that delegate to receivers.

# CORRECT: Command delegates to receiver
class BankAccount:
    def transfer_to(self, other_account, amount):
        if self.balance >= amount:
            self.balance -= amount
            other_account.balance += amount
        else:
            raise ValueError("Insufficient funds")

class GoodTransferCommand(Command):
    def __init__(self, from_account, to_account, amount):
        self.from_account = from_account
        self.to_account = to_account
        self.amount = amount
    
    def execute(self):
        self.from_account.transfer_to(self.to_account, self.amount)

3. Not Clearing Redo Stack on New Command

Mistake: Allowing redo after a new command is executed, leading to inconsistent state.

# WRONG: Redo stack not cleared
class BadEditor:
    def execute(self, command):
        command.execute()
        self.undo_stack.append(command)
        # Missing: self.redo_stack.clear()
    
    # User does: type "A", undo, type "B", redo
    # Result: "AB" (incorrect - should be just "B")

Fix: Clear the redo stack whenever a new command is executed.

# CORRECT: Redo stack cleared
class GoodEditor:
    def execute(self, command):
        command.execute()
        self.undo_stack.append(command)
        self.redo_stack.clear()  # Clear redo history

4. Creating Too Many Command Classes

Mistake: Creating a separate command class for every tiny operation, leading to class explosion.

Fix: Use parameterized commands or lambda functions for simple operations.

# Instead of: TurnOnCommand, TurnOffCommand, DimTo50Command, DimTo75Command...
# Use a parameterized command:

class LightCommand(Command):
    def __init__(self, light, action, *args):
        self.light = light
        self.action = action
        self.args = args
        self.previous_state = None
    
    def execute(self):
        self.previous_state = (self.light.is_on, self.light.brightness)
        self.action(*self.args)
    
    def undo(self):
        # Restore previous state
        pass

# Usage:
command = LightCommand(light, light.dim, 50)

5. Not Handling Command Failure

Mistake: Not considering what happens when a command fails during execution.

# WRONG: No error handling
class RiskyCommand(Command):
    def execute(self):
        self.receiver.risky_operation()  # Might fail!
        self.history.append(self)  # Added even if operation failed

Fix: Handle failures and only add to history if successful.

# CORRECT: Error handling
class SafeCommand(Command):
    def execute(self):
        try:
            self.receiver.risky_operation()
            return True  # Success
        except Exception as e:
            print(f"Command failed: {e}")
            return False  # Failure

class Invoker:
    def execute_command(self, command):
        if command.execute():  # Only add to history if successful
            self.history.append(command)

Interview Tips

What Interviewers Look For

1. Recognize When to Use the Pattern

Interviewers often present scenarios and ask you to identify the appropriate pattern. Command Pattern red flags include:

  • “Design a system that supports undo/redo”
  • “Queue operations for later execution”
  • “Log all user actions for audit”
  • “Implement macro commands or scripting”

When you hear these, immediately think Command Pattern and explain why it fits.

2. Explain the Four Key Benefits

Be ready to articulate these advantages clearly:

  • Decoupling: Invoker doesn’t know about receivers
  • Undo/Redo: Commands store state for reversal
  • Queuing: Commands are objects that can be stored and executed later
  • Composability: Macro commands combine multiple operations

3. Implement Undo Correctly

This is the most common interview question. Show you understand:

  • State must be captured BEFORE execution
  • Undo reverses the operation using saved state
  • Redo re-executes the command
  • New commands clear the redo stack

Code this pattern from memory:

class Command:
    def execute(self):
        self.previous_state = self.receiver.get_state()
        self.receiver.perform_action()
    
    def undo(self):
        self.receiver.set_state(self.previous_state)

4. Compare with Strategy Pattern

Interviewers love asking: “How is Command different from Strategy?”

Answer: Both encapsulate operations, but:

  • Command: Encapsulates a request with its receiver and parameters. Supports undo, queuing, logging. The command object has a lifecycle.
  • Strategy: Encapsulates an algorithm. Focuses on interchangeable behaviors. No undo or queuing.

Example: A text editor uses Command for edit operations (undo needed) but Strategy for spell-checking algorithms (no undo needed).

5. Discuss Trade-offs

Show maturity by discussing when NOT to use the pattern:

  • Overhead: Creates many small classes for simple operations
  • Complexity: Overkill for systems that don’t need undo or queuing
  • Memory: Storing command history consumes memory

Alternative: For simple callbacks without undo, use function pointers or lambdas instead.

Common Interview Questions

Q: “Design a text editor with undo/redo.”

  • Start with Command interface (execute, undo)
  • Create InsertCommand and DeleteCommand
  • Show how Editor maintains undo/redo stacks
  • Demonstrate clearing redo stack on new command

Q: “How would you implement a macro command?”

class MacroCommand(Command):
    def __init__(self, commands):
        self.commands = commands
    
    def execute(self):
        for cmd in self.commands:
            cmd.execute()
    
    def undo(self):
        # Undo in reverse order
        for cmd in reversed(self.commands):
            cmd.undo()

Q: “What if a command in a macro fails?” Discuss transaction-like behavior:

  • Execute all commands
  • If any fails, undo all previous commands in reverse
  • Use try-catch and maintain executed list

Q: “How do you handle commands that can’t be undone?”

  • Create a NonUndoableCommand base class
  • Or: Have undo() throw an exception
  • Or: Return a boolean from canUndo()
  • Disable undo button in UI when such commands are executed

Code Interview Strategy

  1. Start simple: Implement basic Command interface and one concrete command
  2. Add undo: Show state management
  3. Add invoker: Demonstrate decoupling
  4. Extend: Add redo, macro commands, or queuing based on requirements

Don’t try to build everything at once. Interviewers want to see incremental design.

Key Takeaways

  • Command Pattern encapsulates requests as objects, enabling parameterization, queuing, logging, and undo/redo functionality by decoupling the invoker from the receiver.
  • Undo requires capturing state before execution — always save the previous state in the command object before performing the operation so you can reverse it later.
  • Commands delegate to receivers — keep commands thin by having them call methods on receiver objects rather than implementing business logic directly.
  • Clear the redo stack on new commands — when a user executes a new command after undoing, the redo history becomes invalid and must be cleared to maintain consistency.
  • Use the pattern when you need operation queuing, logging, undo/redo, or macro commands — but avoid it for simple callbacks where the overhead of creating command classes isn’t justified.