| Users/Actions | Memory Usage | Undo Stack Size | Latency | Complexity |
|---|---|---|---|---|
| 100 users | Low (few commands stored) | Small (few undo steps) | Instant | Simple stack operations |
| 10,000 users | Moderate (more commands in memory) | Medium (longer undo history) | Still fast | Stack management with pruning |
| 1,000,000 users | High (large command history) | Large (may need limits) | Possible delay if not optimized | Need efficient storage and retrieval |
| 100,000,000 users | Very high (massive data) | Very large (undo history must be limited) | Latency noticeable without optimization | Requires distributed storage and pruning |
Command pattern for undo in LLD - Scalability & System Analysis
Start learning this pattern below
Jump into concepts and practice - no test required
The undo stack storage becomes the first bottleneck as the number of users and commands grows. Storing every command and its state consumes memory and disk space. Also, retrieving and applying undo commands can slow down if the stack is too large or not efficiently managed.
- Limit Undo History: Keep only a fixed number of recent commands per user to reduce memory.
- Command Compression: Store commands in a compact form or only store deltas.
- Persistent Storage: Use databases or disk storage for older commands instead of memory.
- Sharding: Partition undo data by user or session to distribute load.
- Caching: Cache recent undo commands in memory for fast access.
- Asynchronous Processing: Perform heavy undo operations in background threads to avoid blocking.
Assuming each command object is ~1 KB:
- 100 users x 20 undo commands = 2 MB memory
- 10,000 users x 20 commands = 200 MB memory
- 1,000,000 users x 20 commands = 20 GB memory (likely needs disk storage)
- 100,000,000 users x 20 commands = 2 TB storage (requires distributed storage)
Request rate depends on user actions; each undo is a command execution. For 1M users with 1 action per second, system handles ~1M QPS, which requires horizontal scaling and efficient command processing.
Start by explaining the command pattern basics and how undo stacks work. Then discuss how scaling affects memory and latency. Identify the undo stack storage as the bottleneck. Propose limiting history, caching, and sharding as solutions. Use numbers to show understanding of resource needs. Finally, mention asynchronous processing to keep UI responsive.
Your undo stack database handles 1000 QPS. Traffic grows 10x to 10,000 QPS. What do you do first?
Answer: Add read replicas and implement caching for recent undo commands to reduce database load. Also, consider limiting undo history size to reduce storage and processing.
Practice
Command pattern in the context of undo functionality?Solution
Step 1: Understand the role of Command pattern
The Command pattern wraps actions as objects, allowing them to be executed and undone independently.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.Final Answer:
To encapsulate actions as objects with execute and undo methods -> Option DQuick Check:
Command pattern = encapsulate actions for undo [OK]
- Thinking Command pattern modifies UI directly
- Confusing Command pattern with data storage
- Assuming it replaces loops or conditionals
Solution
Step 1: Identify standard Command interface methods
The Command pattern typically defines anexecute()method to perform the action and anundo()method to reverse it.Step 2: Match method signatures
Only void execute(); void undo(); hasexecute()andundo(), matching the Command pattern for undo.Final Answer:
void execute(); void undo(); -> Option BQuick Check:
Command methods = execute and undo [OK]
- Choosing unrelated method names like run/stop
- Confusing start/finish with undo functionality
- Assuming save/load are Command methods
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)Solution
Step 1: Trace command executions
Initially, receiver.total = 0. After cmd1.execute(), total = 0 + 5 = 5. After cmd2.execute(), total = 5 + 3 = 8.Step 2: Apply undo on cmd2
cmd2.undo() subtracts 3, so total = 8 - 3 = 5.Final Answer:
5 -> Option AQuick Check:
Execute adds, undo subtracts = 5 [OK]
- Forgetting to subtract on undo
- Assuming undo resets total to zero
- Mixing order of execute and undo
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)Solution
Step 1: Analyze execute and undo methods
Execute saves previous total and multiplies current total by value. Undo divides total by value.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.Final Answer:
Undo should restore previous value, not divide -> Option CQuick Check:
Undo must restore saved state, not recalculate [OK]
- Assuming division always reverses multiplication
- Not saving previous state before execute
- Ignoring edge cases like zero multiplication
Solution
Step 1: Understand undo/redo requirements
Undo reverses last command, redo reapplies commands undone. Efficient support requires tracking both undo and redo history.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.Final Answer:
Use two stacks: one for undo commands, one for redo commands -> Option AQuick Check:
Two stacks = efficient undo/redo [OK]
- Using single list without tracking position
- Keeping only last command loses history
- Saving full snapshots wastes memory
