Bird
Raised Fist0
LLDsystem_design~15 mins

Command pattern in LLD - Deep Dive

Choose your learning style10 modes available

Start learning this pattern below

Jump into concepts and practice - no test required

or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
Overview - Command pattern
What is it?
The Command pattern is a way to package a request or action as an object. This object holds all the information needed to perform the action later. It separates the object that asks for the action from the one that performs it. This helps in organizing code and making it easier to add new commands without changing existing code.
Why it matters
Without the Command pattern, systems can become tightly linked and hard to change. Every time you want to add a new action, you might have to change many parts of the code. This pattern solves that by making actions independent objects. It allows features like undo, redo, logging, and queuing commands, which are common in real-world applications like text editors or remote controls.
Where it fits
Before learning the Command pattern, you should understand basic object-oriented programming concepts like classes, objects, and methods. After this, you can explore related design patterns like the Observer pattern or the Strategy pattern, which also help organize behavior in flexible ways.
Mental Model
Core Idea
The Command pattern turns requests into objects, letting you store, pass, and execute actions flexibly and independently from the caller.
Think of it like...
Imagine a restaurant where you write your order on a ticket and hand it to the kitchen. The waiter (caller) doesn't cook; the kitchen (receiver) does. The ticket (command object) carries all details needed to prepare your meal, and you can keep, change, or repeat the order anytime.
┌─────────────┐      ┌─────────────┐      ┌─────────────┐
│   Client    │─────▶│  Command    │─────▶│  Receiver   │
│ (Invoker)   │      │ (Request)   │      │ (Action)    │
└─────────────┘      └─────────────┘      └─────────────┘
Build-Up - 6 Steps
1
FoundationUnderstanding basic command encapsulation
🤔
Concept: Learn how to wrap a simple action inside an object with a method to execute it.
Imagine you want to turn on a light. Instead of calling the light's on method directly, you create a command object that has an execute method. When execute is called, it turns the light on. This separates the request from the action.
Result
You get a command object that can turn the light on when asked, without the caller needing to know how the light works.
Understanding that actions can be wrapped as objects is the foundation of the Command pattern and enables flexible control over when and how actions run.
2
FoundationSeparating invoker and receiver roles
🤔
Concept: Distinguish between the object that asks for an action and the one that performs it.
The invoker holds the command object and calls its execute method. The receiver is the object that actually performs the work, like the light. This separation means the invoker doesn't need to know the details of the action.
Result
The invoker can trigger commands without knowing how they work, making the system easier to extend and maintain.
Separating roles reduces dependencies and allows changing or adding commands without touching the invoker.
3
IntermediateImplementing undo and redo functionality
🤔Before reading on: do you think the Command pattern can support undo actions easily? Commit to yes or no.
Concept: Commands can store state to reverse their actions, enabling undo and redo features.
Each command can keep track of what it changed. When undo is called, the command reverses its effect. For example, a command that turns a light on can turn it off on undo. This makes undo/redo possible without complex logic in the invoker.
Result
You can add undo and redo by extending commands, improving user experience in apps like editors or games.
Knowing commands carry their own state and logic for undo helps build powerful, user-friendly systems.
4
IntermediateUsing command queues for delayed execution
🤔Before reading on: do you think commands can be stored and executed later? Commit to yes or no.
Concept: Commands can be placed in a queue to run later or in a specific order.
Instead of executing immediately, commands are added to a list. The invoker processes this list when ready. This allows batching, scheduling, or retrying commands, useful in systems like task schedulers or network requests.
Result
Commands become flexible units of work that can be managed over time, improving control and reliability.
Understanding commands as discrete, storable actions unlocks advanced control flows like scheduling and retries.
5
AdvancedSupporting composite commands for complex actions
🤔Before reading on: do you think multiple commands can be combined into one? Commit to yes or no.
Concept: Composite commands group several commands into one, executing them as a batch.
A composite command holds a list of commands and calls execute on each in order. This lets you treat multiple actions as a single command, simplifying complex workflows like macro recording or multi-step transactions.
Result
You can build complex behaviors from simple commands, improving modularity and reuse.
Recognizing commands can be composed enables scalable and maintainable designs for complex operations.
6
ExpertDecoupling with command serialization and remote execution
🤔Before reading on: can commands be sent over a network to run elsewhere? Commit to yes or no.
Concept: Commands can be serialized into data and sent to other systems for execution, enabling distributed architectures.
By turning commands into data (like JSON), they can be stored, sent over networks, and executed remotely. This supports patterns like job queues, remote procedure calls, or event sourcing in distributed systems.
Result
Commands become portable units of work, allowing flexible, scalable, and decoupled system designs.
Understanding command serialization bridges local design patterns with distributed system architectures.
Under the Hood
Internally, the Command pattern uses objects that implement a common interface with an execute method. The invoker holds a reference to a command object and calls execute without knowing the details. Commands often hold references to receivers and any data needed to perform the action. This encapsulation allows commands to be stored, passed around, or extended with undo logic. The pattern relies on polymorphism to treat all commands uniformly.
Why designed this way?
The pattern was designed to solve tight coupling between requesters and performers of actions. Early software systems had rigid code where adding new commands meant changing many parts. By encapsulating requests as objects, the design promotes open-closed principle: open for extension, closed for modification. Alternatives like direct method calls or large conditional statements were less flexible and harder to maintain.
┌─────────────┐       ┌─────────────┐       ┌─────────────┐
│   Invoker   │──────▶│   Command   │──────▶│  Receiver   │
│ (Client)    │       │ (Interface) │       │ (Performs)  │
└─────────────┘       └─────────────┘       └─────────────┘
       │                    ▲                      ▲
       │                    │                      │
       │            ┌───────┴───────┐       ┌──────┴─────┐
       │            │ ConcreteCommand│       │  Receiver  │
       │            │ (implements execute)│     │  Object    │
       │            └────────────────┘       └───────────┘
Myth Busters - 4 Common Misconceptions
Quick: Does the Command pattern always require undo functionality? Commit to yes or no.
Common Belief:The Command pattern is only useful if you want undo or redo features.
Tap to reveal reality
Reality:Undo/redo is a common use but not required. The pattern mainly decouples requesters from executors and enables flexible command management.
Why it matters:Limiting the pattern to undo use cases misses its broader benefits like queuing, logging, and remote execution.
Quick: Do commands always have to be complex objects with many fields? Commit to yes or no.
Common Belief:Commands must be large objects holding lots of data and logic.
Tap to reveal reality
Reality:Commands can be simple, sometimes just wrapping a single method call with minimal data.
Why it matters:Thinking commands must be complex can discourage their use in simple scenarios where they add clarity and flexibility.
Quick: Is the invoker responsible for knowing how to execute commands internally? Commit to yes or no.
Common Belief:The invoker must understand the details of each command to execute it properly.
Tap to reveal reality
Reality:The invoker only calls execute on commands without knowing their internals, promoting loose coupling.
Why it matters:Misunderstanding this leads to tightly coupled code and defeats the pattern's purpose.
Quick: Can commands be used only within a single application process? Commit to yes or no.
Common Belief:Commands only work inside one program and cannot be sent elsewhere.
Tap to reveal reality
Reality:Commands can be serialized and sent over networks to run remotely, enabling distributed systems.
Why it matters:Ignoring this limits the pattern's use in modern architectures like microservices and cloud computing.
Expert Zone
1
Commands can carry metadata like timestamps or user info to support auditing and security, which many overlook.
2
The pattern enables transactional behavior by grouping commands and rolling back on failure, a subtle but powerful use.
3
Implementing commands as immutable objects prevents side effects and makes concurrent execution safer, a detail often missed.
When NOT to use
Avoid the Command pattern when actions are simple and unlikely to change, as it adds unnecessary complexity. For very high-performance needs where object creation overhead matters, direct method calls may be better. Alternatives include the Strategy pattern for interchangeable algorithms without queuing or undo needs.
Production Patterns
In real systems, commands are used in GUI frameworks for undo/redo, in job schedulers for deferred tasks, and in distributed systems for remote procedure calls. They often integrate with event sourcing to record all changes as commands, enabling replay and audit trails.
Connections
Event Sourcing
Builds-on
Commands represent intent to change state, which event sourcing records as immutable events, linking design patterns to data persistence strategies.
Message Queues
Same pattern in distributed systems
Commands serialized as messages enable asynchronous processing and decoupling in large-scale systems, showing how design patterns scale beyond code.
Legal Contracts
Analogy in law
Like commands, contracts encapsulate agreed actions and conditions, illustrating how encapsulating intent is a universal concept across fields.
Common Pitfalls
#1Trying to execute commands directly without an invoker.
Wrong approach:command.execute(); // called directly everywhere
Correct approach:invoker.setCommand(command); invoker.invoke();
Root cause:Misunderstanding the invoker's role leads to tight coupling and loss of flexibility.
#2Storing commands without implementing undo logic but expecting undo to work.
Wrong approach:class SimpleCommand { execute() { doAction(); } undo() { /* missing */ } }
Correct approach:class UndoableCommand { execute() { doAction(); } undo() { reverseAction(); } }
Root cause:Confusing command execution with undo capability causes incomplete features.
#3Making commands too complex by mixing unrelated logic inside them.
Wrong approach:class Command { execute() { doAction(); logSomething(); updateUI(); } }
Correct approach:class Command { execute() { doAction(); } } // Logging and UI updates handled separately
Root cause:Violating single responsibility principle reduces maintainability and testability.
Key Takeaways
The Command pattern encapsulates actions as objects, separating the requester from the executor.
This separation enables flexible features like undo, redo, queuing, and remote execution.
Commands can be simple or composite, allowing scalable and modular system design.
Understanding the invoker, command, and receiver roles is key to applying the pattern correctly.
The pattern bridges local code organization with distributed system design through command serialization.

Practice

(1/5)
1. What is the main purpose of the Command pattern in system design?
easy
A. To create multiple instances of a class efficiently
B. To ensure only one instance of a class exists
C. To define a family of algorithms and make them interchangeable
D. To encapsulate a request as an object, allowing parameterization and queuing of requests

Solution

  1. Step 1: Understand the Command pattern role

    The Command pattern encapsulates a request as an object, which allows you to parameterize clients with queues, requests, and operations.
  2. Step 2: Compare with other patterns

    Creating multiple instances relates to Prototype or Factory, a family of algorithms to Strategy, and a single instance to Singleton; these are not Command.
  3. Final Answer:

    To encapsulate a request as an object, allowing parameterization and queuing of requests -> Option D
  4. Quick Check:

    Command pattern = encapsulate request [OK]
Hint: Command pattern = wrap action as object for flexibility [OK]
Common Mistakes:
  • Confusing Command with Singleton or Factory patterns
  • Thinking Command creates instances instead of encapsulating actions
  • Mixing Command with Strategy pattern
2. Which of the following is the correct method signature for the execute method in a Command interface?
easy
A. void execute(String[] args);
B. void execute();
C. boolean execute(String commandName);
D. int execute(int commandId);

Solution

  1. Step 1: Recall Command interface basics

    The Command interface typically defines a simple execute() method without parameters to perform the action.
  2. Step 2: Analyze options

    The options with parameters (String[], int commandId, String commandName) or return types are not standard in Command pattern interfaces; the command object itself holds necessary data.
  3. Final Answer:

    void execute(); -> Option B
  4. Quick Check:

    Command execute method = void execute() [OK]
Hint: Command execute usually has no parameters [OK]
Common Mistakes:
  • Adding parameters to execute method unnecessarily
  • Confusing Command with other patterns that require arguments
  • Assuming execute returns a value
3. Given the following code snippet implementing the Command pattern, what will be the output?
class Light {
  turnOn() { console.log('Light is ON'); }
  turnOff() { console.log('Light is OFF'); }
}

class TurnOnCommand {
  constructor(light) { this.light = light; }
  execute() { this.light.turnOn(); }
}

class TurnOffCommand {
  constructor(light) { this.light = light; }
  execute() { this.light.turnOff(); }
}

class RemoteControl {
  setCommand(command) { this.command = command; }
  pressButton() { this.command.execute(); }
}

const light = new Light();
const remote = new RemoteControl();
remote.setCommand(new TurnOnCommand(light));
remote.pressButton();
remote.setCommand(new TurnOffCommand(light));
remote.pressButton();
medium
A. Light is ON\nLight is OFF
B. Light is OFF\nLight is ON
C. Light is ON\nLight is ON
D. Light is OFF\nLight is OFF

Solution

  1. Step 1: Trace first command execution

    The remote sets the command to TurnOnCommand and calls execute, which calls light.turnOn(), printing 'Light is ON'.
  2. Step 2: Trace second command execution

    The remote sets the command to TurnOffCommand and calls execute, which calls light.turnOff(), printing 'Light is OFF'.
  3. Final Answer:

    Light is ON\nLight is OFF -> Option A
  4. Quick Check:

    TurnOn then TurnOff commands print ON then OFF [OK]
Hint: Follow command set and execute calls step-by-step [OK]
Common Mistakes:
  • Mixing order of commands
  • Assuming commands execute immediately without setting
  • Confusing method names turnOn and turnOff
4. In the following code, what is the main issue that prevents the Command pattern from working correctly?
class Light {
  turnOn() { console.log('Light is ON'); }
}

class TurnOnCommand {
  constructor() { }
  execute() { this.light.turnOn(); }
}

const light = new Light();
const command = new TurnOnCommand();
command.execute();
medium
A. The execute method should return a value
B. The Light class is missing the turnOff method
C. The TurnOnCommand constructor does not receive or store the Light object
D. The command object is not instantiated properly

Solution

  1. Step 1: Check TurnOnCommand constructor

    The constructor does not accept or assign the Light object to this.light, so this.light is undefined.
  2. Step 2: Analyze execute method call

    Calling this.light.turnOn() fails because this.light is undefined, causing an error.
  3. Final Answer:

    The TurnOnCommand constructor does not receive or store the Light object -> Option C
  4. Quick Check:

    Missing light reference in command = error [OK]
Hint: Ensure command stores receiver object before execute [OK]
Common Mistakes:
  • Ignoring missing receiver object in command
  • Thinking missing turnOff method causes error here
  • Assuming execute must return a value
5. You are designing a text editor with undo functionality using the Command pattern. Which design choice best supports undo operations efficiently?
hard
A. Store a history stack of Command objects and call an undo() method on the last command
B. Keep a log of all text changes as strings and replay them to undo
C. Use a single Command object that modifies text directly without history
D. Implement undo by reloading the entire document from disk

Solution

  1. Step 1: Understand undo with Command pattern

    Each Command object should implement both execute() and undo() methods to reverse its action.
  2. Step 2: Evaluate design choices

    Storing a history stack of Command objects allows calling undo() on the last command efficiently. Other options either lack command encapsulation or are inefficient.
  3. Final Answer:

    Store a history stack of Command objects and call an undo() method on the last command -> Option A
  4. Quick Check:

    Undo = command history stack with undo() [OK]
Hint: Undo needs command history with undo method [OK]
Common Mistakes:
  • Using string logs instead of command objects
  • Not implementing undo in commands
  • Reloading entire document is inefficient