← Back to principles

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 interface
2interface Command {
3 execute(): void;
4 undo(): void;
5}
6
7// Receiver
8class Light {
9 private isOn: boolean = false;
10
11 turnOn(): void {
12 this.isOn = true;
13 console.log("Light is ON");
14 }
15
16 turnOff(): void {
17 thisisOn

Text Editor with Undo/Redo

1// Command interface
2interface TextCommand {
3 execute(): void;
4 undo(): void;
5}
6
7// Receiver
8class TextEditor {
9 private content: string = "";
10
11 insert(text: string, position: number): void {
12 this.content = this.content.slice(0, position) + text + this.content.slice(position

Job Queue

1// Command interface
2interface Job {
3 execute(): Promise<void>;
4 getName(): string;
5}
6
7// Concrete commands
8class EmailJob implements Job {
9 constructor(
10 private to: string,
11 private subject: string,
12 private body: string
13 ) {}
14
15 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}
5
6class LightOnCommand implements Command {
7 private light: Light;
8
9 execute(): void {
10 this.light.turnOn();
11 }
12
13 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;
5
6 execute(): void {
7 this.editor.insert(this.text, this.position);
8 }
9
10 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;
4
5 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);
8
9 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 interface
2interface TransactionCommand {
3 execute(): Promise<void>;
4 rollback(): Promise<void>;
5 getServiceId(): string;
6 getId(): string;
7}
8
9// Concrete commands
10class TransferCommand implements TransactionCommand {
11 private id: string;
12 private serviceId: string = "payment-service";
13 private fromAccount: string;

Features:

  1. Atomic execution: All commands execute or none
  2. Rollback: Rollback all commands on failure
  3. Logging: Log all transactions
  4. Two-phase commit: Prepare then commit
  5. 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.

Keep exploring

Principles work best in chorus. Pair this lesson with another concept and observe how your architecture conversations change.