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:

bad_remote.py
# ❌ Without Command Pattern - Tight coupling, no undo!
class Light:
def __init__(self, location: str):
self.location = location
self.is_on = False
def turn_on(self):
self.is_on = True
print(f"💡 {self.location} light is ON")
def turn_off(self):
self.is_on = False
print(f"💡 {self.location} light is OFF")
class Fan:
def __init__(self, location: str):
self.location = location
self.speed = 0
def set_speed(self, speed: int):
self.speed = speed
print(f"🌀 {self.location} fan speed: {speed}")
class RemoteControl:
def __init__(self):
# Problem: Remote knows about ALL device types!
self.light = None
self.fan = None
def press_light_on(self):
if self.light:
self.light.turn_on()
def press_light_off(self):
if self.light:
self.light.turn_off()
def press_fan_high(self):
if self.fan:
self.fan.set_speed(3)
def press_fan_off(self):
if self.fan:
self.fan.set_speed(0)
# Problems:
# - Remote tightly coupled to device types
# - Need to modify Remote for each new device
# - No undo functionality
# - No way to queue commands
# Usage
remote = RemoteControl()
remote.light = Light("Living Room")
remote.press_light_on()
# How do we undo? No easy way!

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"
command_remote.py
from abc import ABC, abstractmethod
from typing import List
# Step 1: Define the Command interface
class Command(ABC):
"""Command interface - all commands implement this"""
@abstractmethod
def execute(self) -> None:
"""Execute the command"""
pass
@abstractmethod
def undo(self) -> None:
"""Undo the command"""
pass
# Step 2: Create Receiver classes (devices)
class Light:
"""Receiver - the device that performs the action"""
def __init__(self, location: str):
self.location = location
self.is_on = False
def turn_on(self) -> None:
self.is_on = True
print(f"💡 {self.location} light is ON")
def turn_off(self) -> None:
self.is_on = False
print(f"💡 {self.location} light is OFF")
class Fan:
"""Receiver - fan device"""
def __init__(self, location: str):
self.location = location
self.speed = 0
def set_speed(self, speed: int) -> None:
self.speed = speed
if speed == 0:
print(f"🌀 {self.location} fan is OFF")
else:
print(f"🌀 {self.location} fan speed: {speed}")
def get_speed(self) -> int:
return self.speed
# Step 3: Create Concrete Commands
class LightOnCommand(Command):
"""Concrete command - turn light on"""
def __init__(self, light: Light):
self.light = light
def execute(self) -> None:
self.light.turn_on()
def undo(self) -> None:
self.light.turn_off()
class LightOffCommand(Command):
"""Concrete command - turn light off"""
def __init__(self, light: Light):
self.light = light
def execute(self) -> None:
self.light.turn_off()
def undo(self) -> None:
self.light.turn_on()
class FanHighCommand(Command):
"""Concrete command - set fan to high speed"""
def __init__(self, fan: Fan):
self.fan = fan
self.prev_speed = 0 # Store previous speed for undo
def execute(self) -> None:
self.prev_speed = self.fan.get_speed() # Save for undo
self.fan.set_speed(3)
def undo(self) -> None:
self.fan.set_speed(self.prev_speed)
class FanOffCommand(Command):
"""Concrete command - turn fan off"""
def __init__(self, fan: Fan):
self.fan = fan
self.prev_speed = 0
def execute(self) -> None:
self.prev_speed = self.fan.get_speed()
self.fan.set_speed(0)
def undo(self) -> None:
self.fan.set_speed(self.prev_speed)
# Step 4: Create NoCommand (Null Object Pattern)
class NoCommand(Command):
"""Null command - does nothing"""
def execute(self) -> None:
pass
def undo(self) -> None:
pass
# Step 5: Create the Invoker
class RemoteControl:
"""Invoker - triggers commands"""
def __init__(self, num_slots: int = 4):
self.num_slots = num_slots
self.on_commands: List[Command] = [NoCommand()] * num_slots
self.off_commands: List[Command] = [NoCommand()] * num_slots
self.history: List[Command] = [] # For undo
def set_command(self, slot: int, on_cmd: Command, off_cmd: Command) -> None:
"""Set commands for a slot"""
self.on_commands[slot] = on_cmd
self.off_commands[slot] = off_cmd
print(f"✅ Slot {slot} configured")
def press_on(self, slot: int) -> None:
"""Press the ON button for a slot"""
print(f"\n🔘 Pressing ON button for slot {slot}")
command = self.on_commands[slot]
command.execute()
self.history.append(command)
def press_off(self, slot: int) -> None:
"""Press the OFF button for a slot"""
print(f"\n🔘 Pressing OFF button for slot {slot}")
command = self.off_commands[slot]
command.execute()
self.history.append(command)
def press_undo(self) -> None:
"""Undo the last command"""
if self.history:
print("\n⏪ Pressing UNDO button")
command = self.history.pop()
command.undo()
else:
print("\n⏪ Nothing to undo")
# Step 6: Use the pattern
def main():
# Create receivers (devices)
living_room_light = Light("Living Room")
bedroom_fan = Fan("Bedroom")
# Create commands
light_on = LightOnCommand(living_room_light)
light_off = LightOffCommand(living_room_light)
fan_high = FanHighCommand(bedroom_fan)
fan_off = FanOffCommand(bedroom_fan)
# Create invoker (remote)
remote = RemoteControl()
# Configure remote
remote.set_command(0, light_on, light_off)
remote.set_command(1, fan_high, fan_off)
# Use the remote
remote.press_on(0) # Light ON
remote.press_on(1) # Fan HIGH
remote.press_off(0) # Light OFF
# Undo operations
remote.press_undo() # Light back ON
remote.press_undo() # Fan back to previous speed
remote.press_undo() # Light back OFF
print("\n✅ Command Pattern: Commands encapsulated with undo support!")
if __name__ == "__main__":
main()

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:

bad_editor.py
# ❌ Without Command Pattern - Undo is a nightmare!
class TextEditor:
def __init__(self):
self.content = ""
# Problem: Need to track every change manually!
self.history = []
def type_text(self, text: str, position: int):
# Insert text at position
old_content = self.content
self.content = self.content[:position] + text + self.content[position:]
# Problem: How to undo? Store entire content?
self.history.append(("type", position, text, old_content))
print(f"Typed: '{text}'")
def delete_text(self, start: int, end: int):
# Delete text in range
old_content = self.content
deleted = self.content[start:end]
self.content = self.content[:start] + self.content[end:]
# Problem: Complex undo tracking
self.history.append(("delete", start, end, deleted, old_content))
print(f"Deleted: '{deleted}'")
def undo(self):
if not self.history:
return
# Problem: Complex logic for each operation type!
last_op = self.history.pop()
if last_op[0] == "type":
# Undo type - restore old content
self.content = last_op[3]
elif last_op[0] == "delete":
# Undo delete - restore old content
self.content = last_op[4]
# Problem: Need to handle EVERY operation type!
# - What about bold? italic? find-replace?
# - Code becomes unmaintainable!
# Usage
editor = TextEditor()
editor.type_text("Hello", 0)
editor.type_text(" World", 5)
editor.delete_text(5, 11) # Delete " World"
editor.undo() # Should restore " World" - but complex!

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"
command_editor.py
from abc import ABC, abstractmethod
from typing import List, Optional
from dataclasses import dataclass, field
# Step 1: Create the Document (Receiver)
class Document:
"""Receiver - the document being edited"""
def __init__(self):
self._content = ""
def insert(self, position: int, text: str) -> None:
"""Insert text at position"""
self._content = self._content[:position] + text + self._content[position:]
def delete(self, start: int, end: int) -> str:
"""Delete text in range and return deleted text"""
deleted = self._content[start:end]
self._content = self._content[:start] + self._content[end:]
return deleted
def get_content(self) -> str:
"""Get document content"""
return self._content
def get_length(self) -> int:
"""Get document length"""
return len(self._content)
# Step 2: Define the Command interface
class EditorCommand(ABC):
"""Command interface for editor operations"""
@abstractmethod
def execute(self) -> None:
"""Execute the command"""
pass
@abstractmethod
def undo(self) -> None:
"""Undo the command"""
pass
@property
@abstractmethod
def description(self) -> str:
"""Description of the command for logging"""
pass
# Step 3: Create Concrete Commands
class TypeCommand(EditorCommand):
"""Command to type/insert text"""
def __init__(self, document: Document, text: str, position: int):
self._document = document
self._text = text
self._position = position
def execute(self) -> None:
self._document.insert(self._position, self._text)
def undo(self) -> None:
# Delete the text that was typed
self._document.delete(self._position, self._position + len(self._text))
@property
def description(self) -> str:
return f"Type '{self._text}' at position {self._position}"
class DeleteCommand(EditorCommand):
"""Command to delete text"""
def __init__(self, document: Document, start: int, end: int):
self._document = document
self._start = start
self._end = end
self._deleted_text: str = "" # Store for undo
def execute(self) -> None:
# Store deleted text for undo
self._deleted_text = self._document.delete(self._start, self._end)
def undo(self) -> None:
# Re-insert the deleted text
self._document.insert(self._start, self._deleted_text)
@property
def description(self) -> str:
return f"Delete text from {self._start} to {self._end}"
class ReplaceCommand(EditorCommand):
"""Command to replace text"""
def __init__(self, document: Document, start: int, end: int, new_text: str):
self._document = document
self._start = start
self._end = end
self._new_text = new_text
self._old_text: str = "" # Store for undo
def execute(self) -> None:
# Store old text and replace
self._old_text = self._document.delete(self._start, self._end)
self._document.insert(self._start, self._new_text)
def undo(self) -> None:
# Delete new text and restore old
self._document.delete(self._start, self._start + len(self._new_text))
self._document.insert(self._start, self._old_text)
@property
def description(self) -> str:
return f"Replace '{self._old_text}' with '{self._new_text}'"
# Step 4: Create Macro Command (Composite Command)
class MacroCommand(EditorCommand):
"""Composite command - executes multiple commands"""
def __init__(self, commands: List[EditorCommand], name: str = "Macro"):
self._commands = commands
self._name = name
def execute(self) -> None:
for command in self._commands:
command.execute()
def undo(self) -> None:
# Undo in reverse order!
for command in reversed(self._commands):
command.undo()
@property
def description(self) -> str:
return f"Macro: {self._name} ({len(self._commands)} commands)"
# Step 5: Create the Editor (Invoker)
class TextEditor:
"""Invoker - manages command execution and undo/redo"""
def __init__(self):
self._document = Document()
self._undo_stack: List[EditorCommand] = []
self._redo_stack: List[EditorCommand] = []
def execute_command(self, command: EditorCommand) -> None:
"""Execute a command and add to history"""
command.execute()
self._undo_stack.append(command)
self._redo_stack.clear() # Clear redo stack on new command
print(f"✅ Executed: {command.description}")
print(f" Content: '{self._document.get_content()}'")
def undo(self) -> bool:
"""Undo the last command"""
if not self._undo_stack:
print("⚠️ Nothing to undo")
return False
command = self._undo_stack.pop()
command.undo()
self._redo_stack.append(command)
print(f"⏪ Undone: {command.description}")
print(f" Content: '{self._document.get_content()}'")
return True
def redo(self) -> bool:
"""Redo the last undone command"""
if not self._redo_stack:
print("⚠️ Nothing to redo")
return False
command = self._redo_stack.pop()
command.execute()
self._undo_stack.append(command)
print(f"⏩ Redone: {command.description}")
print(f" Content: '{self._document.get_content()}'")
return True
def get_content(self) -> str:
"""Get document content"""
return self._document.get_content()
def get_document(self) -> Document:
"""Get document for creating commands"""
return self._document
def get_history(self) -> List[str]:
"""Get command history"""
return [cmd.description for cmd in self._undo_stack]
# Step 6: Use the pattern
def main():
# Create editor
editor = TextEditor()
doc = editor.get_document()
print("=" * 50)
print("Text Editor with Undo/Redo (Command Pattern)")
print("=" * 50)
# Type some text
editor.execute_command(TypeCommand(doc, "Hello", 0))
editor.execute_command(TypeCommand(doc, " World", 5))
editor.execute_command(TypeCommand(doc, "!", 11))
# Delete some text
editor.execute_command(DeleteCommand(doc, 5, 11)) # Delete " World"
# Replace text
editor.execute_command(ReplaceCommand(doc, 0, 5, "Hi"))
print("\n" + "-" * 50)
print("Testing Undo/Redo")
print("-" * 50)
# Undo operations
editor.undo() # Undo replace
editor.undo() # Undo delete
editor.undo() # Undo "!"
# Redo some operations
editor.redo() # Redo "!"
editor.redo() # Redo delete
print("\n" + "-" * 50)
print("Testing Macro Command")
print("-" * 50)
# Create a macro command
doc = editor.get_document()
macro = MacroCommand([
TypeCommand(doc, " - Edited", editor.get_document().get_length()),
TypeCommand(doc, " (v2)", editor.get_document().get_length() + 9),
], "Add version tag")
editor.execute_command(macro)
# Undo entire macro at once
editor.undo()
print("\n" + "-" * 50)
print("Command History:")
print("-" * 50)
for i, desc in enumerate(editor.get_history(), 1):
print(f" {i}. {desc}")
print("\n✅ Command Pattern: Full undo/redo with command history!")
if __name__ == "__main__":
main()

There are different ways to implement the Command Pattern:

When undo isn’t needed:

simple_command.py
# Simple Command - no undo needed
from abc import ABC, abstractmethod
class Command(ABC):
@abstractmethod
def execute(self): pass
class PrintCommand(Command):
def __init__(self, message: str):
self.message = message
def execute(self):
print(self.message)
# Usage
cmd = PrintCommand("Hello!")
cmd.execute()

Pros: Simple, lightweight
Cons: No undo support

Using callbacks for results:

callback_command.py
# Command with callback
from typing import Callable, Any
class AsyncCommand:
def __init__(self, action: Callable, callback: Callable[[Any], None]):
self.action = action
self.callback = callback
def execute(self):
result = self.action()
self.callback(result)
# Usage
def fetch_data():
return {"users": [1, 2, 3]}
def handle_result(data):
print(f"Got data: {data}")
cmd = AsyncCommand(fetch_data, handle_result)
cmd.execute()

Pros: Supports async operations
Cons: More complex

Queuing commands for batch execution:

command_queue.py
# Command Queue - batch execution
from collections import deque
from abc import ABC, abstractmethod
class Command(ABC):
@abstractmethod
def execute(self): pass
class CommandQueue:
def __init__(self):
self._queue = deque()
def add(self, command: Command):
self._queue.append(command)
def execute_all(self):
while self._queue:
command = self._queue.popleft()
command.execute()
# Usage
queue = CommandQueue()
queue.add(PrintCommand("First"))
queue.add(PrintCommand("Second"))
queue.add(PrintCommand("Third"))
queue.execute_all() # Executes all in order

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


missing_state.py
# ❌ Bad: Command doesn't store state for undo
class BadDeleteCommand:
def __init__(self, document, start, end):
self.document = document
self.start = start
self.end = end
# Missing: self.deleted_text!
def execute(self):
self.document.delete(self.start, self.end)
def undo(self):
# Problem: What text to restore? We don't know!
pass
# ✅ Good: Command stores state for undo
class GoodDeleteCommand:
def __init__(self, document, start, end):
self.document = document
self.start = start
self.end = end
self.deleted_text = "" # Will store deleted text
def execute(self):
self.deleted_text = self.document.delete(self.start, self.end)
def undo(self):
self.document.insert(self.start, self.deleted_text)
external_state.py
# ❌ Bad: Command modifies external/global state
global_counter = 0
class BadCommand:
def execute(self):
global global_counter
global_counter += 1 # Bad: Modifying global state!
def undo(self):
global global_counter
global_counter -= 1 # Problem: Race conditions!
# ✅ Good: Command only modifies controlled receiver
class GoodCommand:
def __init__(self, counter):
self.counter = counter # Controlled receiver
self.prev_value = 0
def execute(self):
self.prev_value = self.counter.get_value()
self.counter.increment()
def undo(self):
self.counter.set_value(self.prev_value)

Mistake 3: Not Clearing Redo Stack on New Command

Section titled “Mistake 3: Not Clearing Redo Stack on New Command”
redo_stack.py
# ❌ Bad: Not clearing redo stack
class BadEditor:
def __init__(self):
self.undo_stack = []
self.redo_stack = []
def execute(self, command):
command.execute()
self.undo_stack.append(command)
# Missing: self.redo_stack.clear()!
# Problem: Redo after new command causes inconsistency!
# ✅ Good: Clear redo stack on new command
class GoodEditor:
def __init__(self):
self.undo_stack = []
self.redo_stack = []
def execute(self, command):
command.execute()
self.undo_stack.append(command)
self.redo_stack.clear() # Clear redo stack!

  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! 🎮