Design Principle
Command Pattern
Learn the Command pattern: encapsulate a request as an object, allowing you to parameterize clients with different requests, queue operations, and support undo.
Command Pattern
Why This Matters
Think of the Command pattern like a restaurant order. You write down the order (command), give it to the kitchen (invoker), and the kitchen executes it. If the customer changes their mind, you can cancel the order (undo). The Command pattern does the same for code—it encapsulates requests as objects, allowing you to queue them, log them, and undo them.
This matters because some operations need to be queued, logged, or undone. A text editor needs undo/redo. A job queue needs to queue operations. A transaction system needs to log operations. The Command pattern provides a clean way to handle these requirements by treating operations as objects.
In interviews, when someone asks "How would you implement undo/redo?", they're testing whether you understand the Command pattern. Do you know how to encapsulate operations? Can you implement undo? Most engineers can't. They just implement operations directly and wonder why undo is hard.
What Engineers Usually Get Wrong
Engineers often think "Command pattern is only for undo/redo." But Command pattern is useful for many things: queuing operations, logging operations, remote procedure calls, macro recording. Understanding these use cases helps you apply the pattern effectively.
Engineers also don't understand that Command pattern adds indirection. If you only need simple operations that don't need queuing or undo, direct method calls are fine. Use Command pattern when you need queuing, logging, undo, or decoupling.
How This Breaks Systems in the Real World
A service was processing orders. Orders were processed immediately when created. If an order needed to be cancelled, the system had to reverse all the operations manually. This was error-prone and complex. The fix? Use Command pattern. Encapsulate order operations as commands. Now cancellation is just executing an undo command, which is simpler and more reliable.
Another story: A service was processing jobs synchronously. When the system was under load, jobs queued up and blocked. The system became unresponsive. The fix? Use Command pattern. Queue operations as commands. Process them asynchronously. This prevents blocking and improves responsiveness.
What is the Command Pattern?
Command Pattern provides:
- Request encapsulation: Encapsulate request as object
- Decoupling: Decouple invoker from receiver
- Undo/Redo: Support undo and redo operations
- Queue operations: Queue and schedule operations
- Logging: Log and audit operations
Use cases:
- Undo/redo functionality
- Macro recording
- Transaction systems
- Job queues
- Remote procedure calls
Structure
Command (interface)
└─ execute()
└─ undo()
ConcreteCommand (implements Command)
└─ receiver: Receiver
└─ execute() (calls receiver.action())
└─ undo() (reverses action)
Invoker
└─ command: Command
└─ execute() (calls command.execute())
Receiver
└─ action()
Examples
Basic Command Pattern
1// Command interface2interface Command {3 execute(): void;4 undo(): void;5}67// Receiver8class Light {9 private isOn: boolean = false;1011 turnOn(): void {12 this.isOn = true;13 console.log("Light is ON");14 }1516 turnOff(): void {17 thisisOn
Text Editor with Undo/Redo
1// Command interface2interface TextCommand {3 execute(): void;4 undo(): void;5}67// Receiver8class TextEditor {9 private content: string = "";1011 insert(text: string, position: number): void {12 this.content = this.content.slice(0, position) + text + this.content.slice(position
Job Queue
1// Command interface2interface Job {3 execute(): Promise<void>;4 getName(): string;5}67// Concrete commands8class EmailJob implements Job {9 constructor(10 private to: string,11 private subject: string,12 private body: string13 ) {}1415 async execute(): Promise<void> {16 console
Common Pitfalls
- Command bloat: Too many command classes. Fix: Use parameterized commands
- Memory usage: History can consume memory. Fix: Limit history size
- Undo complexity: Complex undo logic. Fix: Store state snapshots
- Not handling errors: Commands fail silently. Fix: Add error handling
Interview Questions
Beginner
Q: What is the Command pattern and when would you use it?
A:
Command Pattern encapsulates a request as an object, allowing you to parameterize clients with different requests.
Key characteristics:
- Request encapsulation: Request becomes an object
- Decoupling: Decouples invoker from receiver
- Undo/Redo: Support undo and redo operations
- Queue operations: Queue and schedule operations
Example:
1interface Command {2 execute(): void;3 undo(): void;4}56class LightOnCommand implements Command {7 private light: Light;89 execute(): void {10 this.light.turnOn();11 }1213 undo(): void {14 this.light.turnOff();15 }16}
Use cases:
- Undo/redo: Text editors, graphics editors
- Macro recording: Record and replay commands
- Job queues: Queue operations for later execution
- Transaction systems: Atomic operations
Benefits:
- Decoupling: Invoker doesn't know receiver
- Undo/redo: Easy to implement
- Queue operations: Schedule commands
- Logging: Log all commands
Intermediate
Q: Explain how the Command pattern supports undo/redo. How do you implement command history?
A:
Command Pattern for Undo/Redo:
1. Command Interface:
1interface Command {2 execute(): void;3 undo(): void;4}
2. Concrete Command:
1class InsertCommand implements Command {2 private editor: TextEditor;3 private text: string;4 private position: number;56 execute(): void {7 this.editor.insert(this.text, this.position);8 }910 undo(): void {11 this.editor.delete(this.position, this.text.length);12 }
3. Command History:
1class CommandHistory {2 private history: Command[] = [];3 private currentIndex: number = -1;45 execute(command: Command): void {6 // Remove commands after current (when doing new command after undo)7 this.history = this.history.slice(0, this.currentIndex + 1);89 command.execute();10 this.history.push(command);11 thiscurrentIndex
Key Points:
- Store commands: Keep executed commands in history
- Track index: Track current position in history
- Remove future: When doing new command after undo, remove future commands
- Execute/undo: Execute for redo, undo for undo
Senior
Q: Design a command system for a distributed transaction system that supports rollback, logging, and ensures commands are executed atomically across multiple services.
A:
1// Command interface2interface TransactionCommand {3 execute(): Promise<void>;4 rollback(): Promise<void>;5 getServiceId(): string;6 getId(): string;7}89// Concrete commands10class TransferCommand implements TransactionCommand {11 private id: string;12 private serviceId: string = "payment-service";13 private fromAccount: string;
Features:
- Atomic execution: All commands execute or none
- Rollback: Rollback all commands on failure
- Logging: Log all transactions
- Two-phase commit: Prepare then commit
- Error handling: Handle failures gracefully
Failure Stories You'll Recognize
The Complex Cancellation: A service was processing orders. Orders were processed immediately when created. If an order needed to be cancelled, the system had to reverse all the operations manually. This was error-prone and complex. The fix? Use Command pattern. Encapsulate order operations as commands. Now cancellation is just executing an undo command, which is simpler and more reliable.
The Blocking Jobs: A service was processing jobs synchronously. When the system was under load, jobs queued up and blocked. The system became unresponsive. The fix? Use Command pattern. Queue operations as commands. Process them asynchronously. This prevents blocking and improves responsiveness.
The Lost Operations: A service was processing operations but didn't log them. When the system crashed, operations were lost. Users had to resubmit. The fix? Use Command pattern. Log commands before executing. If the system crashes, replay commands from the log. This ensures no operations are lost.
What Interviewers Are Really Testing
They want to hear you talk about Command as a tool for managing operations, not just a pattern. Junior engineers say "Command pattern encapsulates requests." Senior engineers say "Command pattern encapsulates operations as objects, enabling queuing, logging, and undo. Use it when you need these features. It adds indirection, so use it only when necessary."
When they ask "How would you implement undo/redo?", they're testing:
-
Do you understand how to encapsulate operations?
-
Can you implement undo/redo?
-
Do you know when Command pattern helps?
-
Command pattern: Encapsulates request as object
-
Decoupling: Decouples invoker from receiver
-
Undo/redo: Easy to implement with command history
-
Queue operations: Queue and schedule commands
-
Use cases: Undo/redo, job queues, transactions, macros
-
Best practices: Store state for undo, handle errors, limit history size
How InterviewCrafted Will Teach This
We'll teach this through real code problems, not abstract patterns. Instead of memorizing "Command pattern encapsulates requests," you'll learn through scenarios like "why is it hard to cancel an order?"
You'll see how Command helps you manage operations, enable undo, and build reliable systems. When an interviewer asks "how would you implement undo/redo?", you'll think about Command pattern, operation history, and state management—not just "store operations."
- Observer Pattern - Both patterns decouple components. Command decouples invoker/receiver, Observer decouples publisher/subscriber.
- Strategy Pattern - Both patterns encapsulate behavior. Commands encapsulate operations, Strategies encapsulate algorithms.
- SOLID Principles - Command pattern follows Single Responsibility Principle (each command does one thing). Understanding SOLID helps understand command benefits.
- Separation of Concerns - Commands separate request from execution. Understanding separation of concerns helps understand command benefits.
- Builder Pattern - Both patterns encapsulate operations. Builders encapsulate construction, Commands encapsulate requests.
Key Takeaways
Command pattern: Encapsulates request as object
Decoupling: Decouples invoker from receiver
Undo/redo: Easy to implement with command history
Queue operations: Queue and schedule commands
Use cases: Undo/redo, job queues, transactions, macros
Best practices: Store state for undo, handle errors, limit history size
Related Topics
Observer Pattern
Both patterns decouple components. Command decouples invoker/receiver, Observer decouples publisher/subscriber.
Strategy Pattern
Both patterns encapsulate behavior. Commands encapsulate operations, Strategies encapsulate algorithms.
SOLID Principles
Command pattern follows Single Responsibility Principle (each command does one thing). Understanding SOLID helps understand command benefits.
Separation of Concerns
Commands separate request from execution. Understanding separation of concerns helps understand command benefits.
Builder Pattern
Both patterns encapsulate operations. Builders encapsulate construction, Commands encapsulate requests.
Keep exploring
Principles work best in chorus. Pair this lesson with another concept and observe how your architecture conversations change.