Skip to content
Low Level Design Mastery Logo
LowLevelDesign Mastery

Command Pattern

Encapsulate requests as objects - queue, log, and undo operations with full control!

Now let’s dive into the Command Pattern - one of the most versatile behavioral design patterns that encapsulates a request as an object, letting you parameterize clients with different requests, queue operations, log them, and support undoable operations.

Imagine you’re using a text editor. You type some text, then click Undo - the text disappears. Click Redo - it comes back. How does this work? The Command Pattern! Each action (typing, deleting, formatting) is wrapped in a command object that knows how to execute itself AND how to undo itself.

The Command Pattern turns requests into stand-alone objects 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.

The Command Pattern is useful when:

  1. You need undo/redo functionality - Commands can be reversed
  2. You want to queue operations - Execute commands later or in sequence
  3. You need to log operations - Commands can be serialized and logged
  4. You want to decouple sender from receiver - Invoker doesn’t know about receiver
  5. You need transactional behavior - Rollback if something fails

What Happens If We Don’t Use Command Pattern?

Section titled “What Happens If We Don’t Use Command Pattern?”

Without the Command Pattern, you might:

  • Tight coupling - Invoker directly calls receiver methods
  • No undo support - Have to manually track all changes
  • Hard to queue operations - Operations execute immediately
  • No operation logging - Can’t track what was done
  • Scattered code - Same operation code repeated everywhere

Let’s start with a super simple example that anyone can understand!

Diagram

Here’s how the Command Pattern works in practice - showing how commands are executed and undone:

sequenceDiagram
    participant Client
    participant Invoker as RemoteControl
    participant Command as LightOnCommand
    participant Receiver as Light
    
    Client->>Command: new LightOnCommand(light)
    activate Command
    Command->>Command: Store light reference
    Command-->>Client: Command created
    deactivate Command
    
    Client->>Invoker: set_command(lightOnCommand)
    Invoker->>Invoker: Store command
    
    Client->>Invoker: press_button()
    activate Invoker
    Invoker->>Command: execute()
    activate Command
    Command->>Receiver: turn_on()
    activate Receiver
    Receiver->>Receiver: Light is ON
    Receiver-->>Command: Done
    deactivate Receiver
    Command-->>Invoker: Executed
    deactivate Command
    Invoker->>Invoker: Store for undo
    Invoker-->>Client: Done
    deactivate Invoker
    
    Note over Client,Receiver: User changes mind...
    
    Client->>Invoker: press_undo()
    activate Invoker
    Invoker->>Command: undo()
    activate Command
    Command->>Receiver: turn_off()
    activate Receiver
    Receiver->>Receiver: Light is OFF
    Receiver-->>Command: Done
    deactivate Receiver
    Command-->>Invoker: Undone
    deactivate Command
    Invoker-->>Client: Done
    deactivate Invoker

You’re building a smart home system with a remote control. The remote needs to control different devices (lights, fans, TVs). Without Command Pattern:

Problems:

  • Remote control is tightly coupled to all device types
  • Adding new devices requires modifying RemoteControl
  • No undo functionality - can’t reverse actions
  • No way to queue or log commands
classDiagram
    class Command {
        <<interface>>
        +execute() void
        +undo() void
    }
    class LightOnCommand {
        -light: Light
        +execute() void
        +undo() void
    }
    class LightOffCommand {
        -light: Light
        +execute() void
        +undo() void
    }
    class FanHighCommand {
        -fan: Fan
        -prevSpeed: int
        +execute() void
        +undo() void
    }
    class RemoteControl {
        -command: Command
        -history: List~Command~
        +set_command(cmd) void
        +press_button() void
        +press_undo() void
    }
    class Light {
        +turn_on() void
        +turn_off() void
    }
    class Fan {
        +set_speed(speed) void
    }
    
    Command <|.. LightOnCommand : implements
    Command <|.. LightOffCommand : implements
    Command <|.. FanHighCommand : implements
    RemoteControl --> Command : invokes
    LightOnCommand --> Light : controls
    LightOffCommand --> Light : controls
    FanHighCommand --> Fan : controls
    
    note for Command "Encapsulates action\nand its undo"
    note for RemoteControl "Invoker - doesn't know\nabout receivers"

Real-World Software Example: Text Editor with Undo/Redo

Section titled “Real-World Software Example: Text Editor with Undo/Redo”

Now let’s see a realistic software example - a text editor that supports full undo/redo functionality.

You’re building a text editor that needs to support multiple operations (typing, deleting, formatting) with full undo/redo capability. Without Command Pattern:

Problems:

  • Complex undo tracking for each operation type
  • Need to modify editor for each new operation
  • History management is complex and error-prone
  • No redo support
  • Hard to serialize/log operations
classDiagram
    class EditorCommand {
        <<interface>>
        +execute() void
        +undo() void
    }
    class TypeCommand {
        -document: Document
        -text: str
        -position: int
        +execute() void
        +undo() void
    }
    class DeleteCommand {
        -document: Document
        -start: int
        -end: int
        -deletedText: str
        +execute() void
        +undo() void
    }
    class BoldCommand {
        -document: Document
        -start: int
        -end: int
        +execute() void
        +undo() void
    }
    class Editor {
        -document: Document
        -undoStack: List~EditorCommand~
        -redoStack: List~EditorCommand~
        +execute_command(cmd) void
        +undo() void
        +redo() void
    }
    class Document {
        -content: str
        +insert(pos, text) void
        +delete(start, end) str
        +get_content() str
    }
    
    EditorCommand <|.. TypeCommand : implements
    EditorCommand <|.. DeleteCommand : implements
    EditorCommand <|.. BoldCommand : implements
    Editor --> EditorCommand : manages
    TypeCommand --> Document : modifies
    DeleteCommand --> Document : modifies
    BoldCommand --> Document : modifies
    Editor --> Document : owns
    
    note for Editor "Manages undo/redo stacks"
    note for EditorCommand "Each command knows\nhow to undo itself"

There are different ways to implement the Command Pattern:

When undo isn’t needed:

Pros: Simple, lightweight
Cons: No undo support

Using callbacks for results:

Pros: Supports async operations
Cons: More complex

Queuing commands for batch execution:


Use Command Pattern when:

You need undo/redo - Commands know how to reverse themselves
You want to queue operations - Execute later or in sequence
You need operation logging - Commands can be serialized and logged
You want to decouple - Invoker doesn’t know about receiver
You need transactional behavior - Rollback if something fails

Don’t use Command Pattern when:

Simple operations - Direct method calls are clearer
No undo needed - Overhead isn’t justified
No operation history - Don’t need logging or auditing
Performance critical - Command objects add overhead
Over-engineering - Don’t add complexity for simple cases


Mistake 3: Not Clearing Redo Stack on New Command

Section titled “Mistake 3: Not Clearing Redo Stack on New Command”

  1. Decoupling - Invoker doesn’t know about receiver
  2. Undo/Redo - Commands know how to reverse themselves
  3. Queueing - Commands can be queued for later execution
  4. Logging - Commands can be serialized and logged
  5. Transactions - Group commands for atomic execution
  6. Macro Commands - Combine multiple commands into one

Command Pattern is a behavioral design pattern that turns a request into a stand-alone object containing all information about the request. This lets you parameterize methods with different requests, delay or queue a request’s execution, and support undoable operations.

  • Undo/Redo - Commands know how to reverse
  • Queueing - Execute commands later
  • Logging - Track all operations
  • Decoupling - Invoker doesn’t know receiver
  • Macro commands - Combine operations
  1. Define Command interface - execute() and undo() methods
  2. Create Concrete Commands - Each wraps a receiver action
  3. Create Receiver - The object that performs the action
  4. Create Invoker - Triggers commands, manages history
  5. Client - Creates commands and configures invoker
Client → creates → Command → calls → Receiver
Invoker → invokes → Command
  • Command - Interface with execute() and undo()
  • Concrete Command - Wraps receiver and action
  • Receiver - Performs the actual work
  • Invoker - Triggers commands, stores history
  • Client - Creates and configures commands
class Command(ABC):
@abstractmethod
def execute(self): pass
@abstractmethod
def undo(self): pass
class LightOnCommand(Command):
def __init__(self, light):
self.light = light
def execute(self):
self.light.turn_on()
def undo(self):
self.light.turn_off()
class RemoteControl:
def __init__(self):
self.history = []
def execute(self, command):
command.execute()
self.history.append(command)
def undo(self):
if self.history:
self.history.pop().undo()

✅ Need undo/redo functionality
✅ Need to queue operations
✅ Need to log all operations
✅ Need to decouple invoker from receiver
✅ Need transactional behavior

❌ Simple operations
❌ No undo needed
❌ No operation history needed
❌ Over-engineering simple cases

  • Command Pattern = Operations as objects
  • Command = Encapsulates action and undo
  • Invoker = Triggers commands, manages history
  • Receiver = Performs actual work
  • Benefit = Undo, queueing, logging, decoupling
# 1. Command Interface
class Command(ABC):
@abstractmethod
def execute(self): pass
@abstractmethod
def undo(self): pass
# 2. Concrete Command
class ConcreteCommand(Command):
def __init__(self, receiver):
self.receiver = receiver
self.prev_state = None
def execute(self):
self.prev_state = self.receiver.get_state()
self.receiver.action()
def undo(self):
self.receiver.set_state(self.prev_state)
# 3. Invoker
class Invoker:
def __init__(self):
self.history = []
def execute(self, command):
command.execute()
self.history.append(command)
def undo(self):
if self.history:
self.history.pop().undo()
  • Command Pattern encapsulates operations as objects
  • It enables undo/redo by storing state
  • It supports queueing and logging operations
  • Use it when you need operation history
  • Don’t use it for simple direct calls!

What to say:

“Command Pattern is a behavioral design pattern that encapsulates a request as an object. This allows you to parameterize clients with different requests, queue operations, log them, and support undoable operations. The command object contains all information needed to perform an action.”

Why it matters:

  • Shows you understand the fundamental purpose
  • Demonstrates knowledge of encapsulation
  • Indicates you can explain concepts clearly

Must mention:

  • Undo/Redo - Each command knows how to reverse itself
  • Operation queueing - Execute later or in batch
  • Logging/Auditing - Commands can be serialized
  • Decoupling - Invoker doesn’t know about receiver
  • Transactional behavior - Rollback on failure

Example scenario to give:

“I’d use Command Pattern when building a text editor. Each operation - typing, deleting, formatting - is a command object. This makes undo/redo trivial: just pop from undo stack, call undo(), push to redo stack. We can also log all commands for crash recovery or collaboration features.”

Must discuss:

  • Command - Encapsulates an operation with its state, supports undo
  • Strategy - Encapsulates an algorithm, algorithms are interchangeable
  • Key difference - Command has state and undo, Strategy is stateless

Example to give:

“Command Pattern encapsulates an operation - ‘delete text at position 5’ - and knows how to undo it. Strategy Pattern encapsulates an algorithm - ‘sort using QuickSort’ - but doesn’t track state. Commands remember what they did so they can undo it; strategies just execute algorithms.”

Must explain:

  1. Undo Stack - Stores executed commands
  2. Redo Stack - Stores undone commands
  3. Execute - Execute command, push to undo stack, clear redo stack
  4. Undo - Pop from undo, call undo(), push to redo
  5. Redo - Pop from redo, call execute(), push to undo

Code to have ready:

class Editor:
def __init__(self):
self.undo_stack = []
self.redo_stack = []
def execute(self, command):
command.execute()
self.undo_stack.append(command)
self.redo_stack.clear() # Important!
def undo(self):
if self.undo_stack:
cmd = self.undo_stack.pop()
cmd.undo()
self.redo_stack.append(cmd)
def redo(self):
if self.redo_stack:
cmd = self.redo_stack.pop()
cmd.execute()
self.undo_stack.append(cmd)

Benefits to mention:

  • Decoupling - Invoker doesn’t know about receiver
  • Undo/Redo - Commands are reversible
  • Queueing - Commands can be queued
  • Logging - Commands can be serialized
  • Macro Commands - Combine multiple commands

Trade-offs to acknowledge:

  • Complexity - More classes than direct calls
  • Memory - Commands store state for undo
  • Overhead - Command objects have cost
  • Overkill for simple cases - Direct calls are simpler

Q: “How would you implement undo in a text editor?”

A:

“I’d use Command Pattern. Each operation - TypeCommand, DeleteCommand, FormatCommand - stores the state needed to undo itself. DeleteCommand stores the deleted text. When execute() is called, it deletes and stores what was deleted. When undo() is called, it restores the deleted text. The editor maintains undo/redo stacks to track command history.”

Q: “How does Command Pattern support transactions?”

A:

“You can create a TransactionCommand that holds multiple commands. On execute(), it executes all commands in sequence. If any fails, it calls undo() on all previously executed commands in reverse order. This gives atomic, all-or-nothing behavior - either all commands succeed or all are rolled back.”

Q: “How does Command Pattern relate to SOLID principles?”

A:

“Command Pattern supports Single Responsibility - each command handles one operation. It supports Open/Closed - add new commands without modifying invoker. It supports Dependency Inversion - invoker depends on Command interface, not concrete commands. It also supports Interface Segregation - Command interface is focused (execute, undo).”

Before your interview, make sure you can:

  • Define Command Pattern clearly in one sentence
  • Explain when to use it (undo, queueing, logging)
  • Describe the structure: Command, Invoker, Receiver
  • Implement undo/redo from scratch
  • Compare with Strategy Pattern
  • List benefits and trade-offs
  • Connect to SOLID principles
  • Identify when NOT to use it
  • Give 2-3 real-world examples (editors, transactions, GUI)
  • Discuss Macro Commands (composite)

Remember: Command Pattern is about encapsulating operations as objects - enabling undo, queueing, and logging with full control over execution! 🎮