Bird
Raised Fist0
LLDsystem_design~25 mins

Command pattern for undo in LLD - System Design Exercise

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
Design: Undo functionality using Command Pattern
Design the command pattern structure to support undo functionality in a local application. Out of scope: redo functionality, distributed undo, or persistent undo history.
Functional Requirements
FR1: Support executing commands that change system state
FR2: Allow undoing the last executed command
FR3: Support multiple undo operations in sequence
FR4: Commands should be encapsulated as objects
FR5: Maintain a history of executed commands for undo
FR6: Ensure commands can be extended easily for new operations
Non-Functional Requirements
NFR1: Undo operations must be efficient with minimal latency
NFR2: Support up to 1000 commands in undo history
NFR3: System should be modular and easy to maintain
NFR4: Memory usage should be optimized for command history
Think Before You Design
Questions to Ask
❓ Question 1
❓ Question 2
❓ Question 3
❓ Question 4
❓ Question 5
Key Components
Command interface with execute and undo methods
Concrete command classes implementing specific actions
Invoker to execute commands and manage history
Receiver which performs the actual operations
Command history stack to track executed commands
Design Patterns
Command pattern for encapsulating requests
Stack data structure for undo history
Memento pattern for saving object state (optional)
Composite pattern if commands can be grouped
Reference Architecture
Client
  |
Invoker (Command Manager)
  |
Command Interface <---- Concrete Commands ----> Receiver
  |
Command History Stack (for undo)
Components
Command Interface
Abstract class or interface
Defines execute() and undo() methods for commands
Concrete Commands
Classes implementing Command Interface
Encapsulate specific actions and their undo logic
Invoker (Command Manager)
Class with command execution and history stack
Executes commands and manages undo stack
Receiver
Domain-specific classes
Performs actual operations requested by commands
Command History Stack
Stack data structure
Stores executed commands for undo operations
Request Flow
1. Client creates a Concrete Command with a Receiver and parameters
2. Client passes command to Invoker to execute
3. Invoker calls command.execute(), which triggers Receiver action
4. Invoker pushes command onto Command History Stack
5. When undo is requested, Invoker pops last command from stack
6. Invoker calls command.undo(), which reverses Receiver action
Database Schema
Not applicable for local command pattern design; focus is on in-memory command objects and history stack.
Scaling Discussion
Bottlenecks
Memory usage grows with large command history
Undo latency if commands have complex undo logic
Difficulty managing commands that affect shared state
Extending commands for new operations may increase complexity
Solutions
Limit command history size with a fixed max stack length
Optimize undo logic in commands for performance
Use careful synchronization if commands modify shared state
Use inheritance and interfaces to keep command extensions clean
Interview Tips
Time: Spend 10 minutes explaining the command pattern basics and undo requirements, 15 minutes designing the components and data flow, 10 minutes discussing scaling and edge cases, and 10 minutes answering questions.
Explain how command pattern encapsulates actions and undo logic
Describe the role of Invoker and command history stack
Discuss how undo reverses the last executed command
Mention handling of commands that cannot be undone
Talk about memory and performance considerations for undo history

Practice

(1/5)
1. What is the main purpose of the Command pattern in the context of undo functionality?
easy
A. To replace all conditional statements with loops
B. To directly modify the user interface without storing history
C. To store data in a database for permanent record
D. To encapsulate actions as objects with execute and undo methods

Solution

  1. Step 1: Understand the role of Command pattern

    The Command pattern wraps actions as objects, allowing them to be executed and undone independently.
  2. Step 2: Relate to undo functionality

    This wrapping enables storing commands in a history stack, so undo can call the undo method on the last command.
  3. Final Answer:

    To encapsulate actions as objects with execute and undo methods -> Option D
  4. Quick Check:

    Command pattern = encapsulate actions for undo [OK]
Hint: Command pattern wraps actions for undo/redo [OK]
Common Mistakes:
  • Thinking Command pattern modifies UI directly
  • Confusing Command pattern with data storage
  • Assuming it replaces loops or conditionals
2. Which method signature correctly belongs to a Command interface supporting undo?
easy
A. void save(); void load();
B. void execute(); void undo();
C. void start(); void finish();
D. void run(); void stop();

Solution

  1. Step 1: Identify standard Command interface methods

    The Command pattern typically defines an execute() method to perform the action and an undo() method to reverse it.
  2. Step 2: Match method signatures

    Only void execute(); void undo(); has execute() and undo(), matching the Command pattern for undo.
  3. Final Answer:

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

    Command methods = execute and undo [OK]
Hint: Look for execute() and undo() methods [OK]
Common Mistakes:
  • Choosing unrelated method names like run/stop
  • Confusing start/finish with undo functionality
  • Assuming save/load are Command methods
3. Given the following code snippet, what will be the output after calling undo() on the last command?
class AddCommand:
    def __init__(self, value, receiver):
        self.value = value
        self.receiver = receiver
    def execute(self):
        self.receiver.total += self.value
    def undo(self):
        self.receiver.total -= self.value

class Receiver:
    def __init__(self):
        self.total = 0

receiver = Receiver()
cmd1 = AddCommand(5, receiver)
cmd2 = AddCommand(3, receiver)
cmd1.execute()
cmd2.execute()
cmd2.undo()
print(receiver.total)
medium
A. 5
B. 8
C. 3
D. 0

Solution

  1. Step 1: Trace command executions

    Initially, receiver.total = 0. After cmd1.execute(), total = 0 + 5 = 5. After cmd2.execute(), total = 5 + 3 = 8.
  2. Step 2: Apply undo on cmd2

    cmd2.undo() subtracts 3, so total = 8 - 3 = 5.
  3. Final Answer:

    5 -> Option A
  4. Quick Check:

    Execute adds, undo subtracts = 5 [OK]
Hint: Undo reverses last execute effect on total [OK]
Common Mistakes:
  • Forgetting to subtract on undo
  • Assuming undo resets total to zero
  • Mixing order of execute and undo
4. Identify the bug in this undo implementation of a Command pattern:
class MultiplyCommand:
    def __init__(self, value, receiver):
        self.value = value
        self.receiver = receiver
        self.prev = None
    def execute(self):
        self.prev = self.receiver.total
        self.receiver.total *= self.value
    def undo(self):
        self.receiver.total /= self.value

receiver = type('Receiver', (), {'total': 10})()
cmd = MultiplyCommand(2, receiver)
cmd.execute()
cmd.undo()
print(receiver.total)
medium
A. Execute should add instead of multiply
B. Undo method is missing
C. Undo should restore previous value, not divide
D. Receiver class is not defined

Solution

  1. Step 1: Analyze execute and undo methods

    Execute saves previous total and multiplies current total by value. Undo divides total by value.
  2. Step 2: Identify problem with undo

    Undo divides by value, but if value is zero or changed, this may not restore original total exactly. It should restore saved previous total instead.
  3. Final Answer:

    Undo should restore previous value, not divide -> Option C
  4. Quick Check:

    Undo must restore saved state, not recalculate [OK]
Hint: Undo must restore saved state, not recalculate [OK]
Common Mistakes:
  • Assuming division always reverses multiplication
  • Not saving previous state before execute
  • Ignoring edge cases like zero multiplication
5. You are designing a text editor with undo using the Command pattern. Which approach best supports multiple undo and redo operations efficiently?
hard
A. Use two stacks: one for undo commands, one for redo commands
B. Store all commands in a single list without pointers
C. Only keep the last command for undo, discard others
D. Save full document snapshots after each command

Solution

  1. Step 1: Understand undo/redo requirements

    Undo reverses last command, redo reapplies commands undone. Efficient support requires tracking both undo and redo history.
  2. Step 2: Evaluate data structures

    Two stacks allow pushing commands on execute, popping for undo, and pushing undone commands to redo stack. This supports multiple undo/redo efficiently.
  3. Final Answer:

    Use two stacks: one for undo commands, one for redo commands -> Option A
  4. Quick Check:

    Two stacks = efficient undo/redo [OK]
Hint: Two stacks handle undo and redo efficiently [OK]
Common Mistakes:
  • Using single list without tracking position
  • Keeping only last command loses history
  • Saving full snapshots wastes memory