Behavioral PatternsCommand PatternHard⏱️ ~3 min

Command Pattern: Interview Questions & Variations

Common Interview Questions

Q1: How would you implement multi-level undo/redo?

Use two stacks: undoStack and redoStack. When executing a new command, push it to undoStack and clear redoStack. When undoing, pop from undoStack, call undo(), and push to redoStack. When redoing, pop from redoStack, call execute(), and push back to undoStack. If undoStack reaches size limit (e.g., 100 operations), remove oldest commands to manage memory. For persistence, serialize command objects to JSON and restore on app restart, but only if commands contain all necessary state.

Q2: How do you handle commands that cannot be undone?

Mark such commands as non-undoable using a flag: isUndoable(): boolean. When executing a non-undoable command like SendEmailCommand, clear the undo history because you cannot roll back past that point. Alternatively, create a Checkpoint before non-undoable commands, warning users that undo will not work beyond this point. However, if the operation is truly irreversible and not beneficial to queue or log, consider whether Command Pattern is appropriate instead of direct method calls.

Q3: What is the difference between Command and Strategy patterns?

Command encapsulates a request (including receiver and parameters) and supports undo/queuing. Strategy encapsulates an algorithm without receiver context. Command has execute() and undo(). Strategy typically has a single method like calculate(). Use Command when operations are reversible or need queuing. Use Strategy when selecting different algorithms for the same task. For example, SortCommand might use different SortStrategy implementations, combining both patterns.

Advanced Variations

Composite Command (Macro): A MacroCommand holds a list of commands and executes them in sequence. execute() loops through all commands. undo() loops in reverse order. This implements Composite Pattern within Command Pattern. Use case: User records a macro of "Select All, Bold, Center Align" and replays it.

MacroCommand
- commands: List<Command>
+ addCommand(cmd): void
+ execute(): void
+ undo(): void

Transactional Commands: A TransactionCommand wraps multiple commands and provides all-or-nothing execution. If any command fails, execute() undoes all previously executed commands and throws an error. Use case: Banking system where transferring money involves DebitCommand and CreditCommand that must both succeed or both fail.

Asynchronous Commands: Commands that perform async operations (API calls, file I/O) return a Promise or Future. execute() returns Promise<void>. The CommandHistory must handle async execution order. Use case: SaveToCloudCommand uploads a file and can be undone by DeleteFromCloudCommand. However, network failures complicate undo logic. Consider if synchronous commands with retry logic are simpler and appropriate instead.

Machine Coding Considerations

First, start with interface design. Define Command interface before concrete classes. Second, implement one concrete command fully with undo logic before adding others. Third, build CommandHistory incrementally: execute first, then undo, then redo. Fourth, demonstrate composition with at least one MacroCommand if time permits. Fifth, handle edge cases: empty undo stack, redo after new command, undo limit reached.

Code structure: Avoid putting business logic in commands. Keep BoldCommand.execute() as a thin wrapper calling editor.applyBold(). All domain logic stays in the Receiver. Commands only coordinate and store undo state.

Testing approach: Test commands individually with mock receivers. Test CommandHistory with simple stub commands. Verify undo/redo state transitions. Check that redo clears on new command execution.

Interview Tip: If asked to implement a remote control system for home automation (common Command Pattern interview question), ensure each device (TV, Light, Thermostat) is a separate Receiver. Commands like TurnOnLightCommand hold a reference to Light receiver. Remote buttons are Invokers that store and execute commands. Support undo by storing previous state (e.g., previous brightness level).

Common Mistakes to Avoid

First, putting too much logic in commands. Commands should delegate to receivers. If BoldCommand manipulates text directly, you cannot reuse that logic elsewhere. Second, forgetting to clear redo stack on new command execution. After undo, executing a new command invalidates all redo history. Third, not handling partial failures in macro commands. If the third command in a macro fails, undo the first two. Fourth, storing receivers in commands as shared mutable state. Each command should have its own receiver reference or accept it in execute(). Fifth, over-generalizing commands. Making a single GenericCommand with lambda functions defeats the purpose. Each command class provides type safety and explicit undo logic.

💡 Key Takeaways
Multi-level undo uses two stacks: undoStack and redoStack
Non-undoable commands should clear undo history or create checkpoints
MacroCommand combines multiple commands using Composite Pattern
Transactional commands provide all-or-nothing execution semantics
Keep business logic in receivers, commands only coordinate and store state
📌 Examples
1MacroCommand for "Select All + Bold + Center" operation
2TransactionCommand for atomic bank transfers
3Remote control with per-button commands and undo functionality
← Back to Command Pattern Overview