Bird
Raised Fist0
Interview Prepoop-design-patternshardAmazonGoogleFlipkartCREDRazorpay

Command Pattern - Undo/Redo, Request Queuing & Logging

Choose your preparation mode3 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
Steps
setup

Initialize Receiver and Invoker

Create the Receiver object which holds the state and the Invoker which manages command execution and undo/redo stacks.

💡 Setting up these objects is essential because the Receiver performs actions, and the Invoker controls command execution and history.
Line:receiver = Receiver() invoker = Invoker()
💡 The Receiver and Invoker are distinct roles: Receiver knows how to perform actions; Invoker manages command lifecycle.
📊
Command Pattern - Undo/Redo, Request Queuing & Logging - Watch the Algorithm Execute, Step by Step
Watching the algorithm execute step-by-step reveals how commands are managed, how undo and redo stacks interact, and how queued commands are processed, which is difficult to grasp from code alone.
Step 1/8
·Active fillAnswer cell
Separation of concerns: Receiver handles state changes; Invoker manages command execution and history stacks.
Receiver
state: int
+action()
+revert()
Invoker
undo_stack: List<Command>
redo_stack: List<Command>
command_queue: List<Command>
+execute_command()
+undo()
+redo()
+2 more
Command encapsulation and execution with history tracking.
AddCommand
receiver: Receiver
value: int
+execute()
+undo()
Receiver
state: int
+action()
+revert()
Invoker
undo_stack: List<Command>
redo_stack: List<Command>
command_queue: List<Command>
+execute_command()
+undo()
+redo()
+2 more
AddCommand Receiver (1:1)
Sequential command execution with undo stack growth.
AddCommand
receiver: Receiver
value: int
+execute()
+undo()
Receiver
state: int
+action()
+revert()
Invoker
undo_stack: List<Command>
redo_stack: List<Command>
command_queue: List<Command>
+execute_command()
+undo()
+redo()
+2 more
AddCommand Receiver (1:1)
Undo operation moves command from undo to redo stack and reverts receiver state.
AddCommand
receiver: Receiver
value: int
+execute()
+undo()
Receiver
state: int
+action()
+revert()
Invoker
undo_stack: List<Command>
redo_stack: List<Command>
command_queue: List<Command>
+execute_command()
+undo()
+redo()
+2 more
AddCommand Receiver (1:1)
Redo operation moves command from redo to undo stack and reapplies receiver state.
AddCommand
receiver: Receiver
value: int
+execute()
+undo()
Receiver
state: int
+action()
+revert()
Invoker
undo_stack: List<Command>
redo_stack: List<Command>
command_queue: List<Command>
+execute_command()
+undo()
+redo()
+2 more
AddCommand Receiver (1:1)
Command queue stores commands for batch execution later.
AddCommand
receiver: Receiver
value: int
+execute()
+undo()
Receiver
state: int
+action()
+revert()
Invoker
undo_stack: List<Command>
redo_stack: List<Command>
command_queue: List<Command>
+execute_command()
+undo()
+redo()
+2 more
AddCommand Receiver (1:1)
Batch execution integrates queued commands into undo/redo stacks.
AddCommand
receiver: Receiver
value: int
+execute()
+undo()
Receiver
state: int
+action()
+revert()
Invoker
undo_stack: List<Command>
redo_stack: List<Command>
command_queue: List<Command>
+execute_command()
+undo()
+redo()
+2 more
AddCommand Receiver (1:1)
Final state shows command pattern managing state and history stacks correctly.
Receiver
state: int
+action()
+revert()
Invoker
undo_stack: List<Command>
redo_stack: List<Command>
command_queue: List<Command>
+execute_command()
+undo()
+redo()
+2 more
AddCommand Receiver (1:1)

Key Takeaways

Commands encapsulate all information needed to perform and undo an action, enabling flexible execution and history management.

This insight is hard to see from code alone because the separation of command logic from execution context is subtle without visualization.

Undo and redo stacks maintain a clear history of executed and undone commands, allowing precise state rollback and reapplication.

Visualizing stack changes clarifies how commands move between undo and redo, which is often confusing when reading code.

Queuing commands allows batching multiple operations for later execution, integrating seamlessly with undo/redo mechanisms.

Seeing queued commands and their execution order helps understand how deferred execution fits into the pattern.

Practice

(1/5)
1. You need to create different types of vehicles (cars, bikes) that share a common interface, and sometimes you want to create entire families of related vehicles (e.g., electric car + electric bike) ensuring consistency. Which design pattern best fits this requirement?
easy
A. Builder Pattern, because it constructs complex objects step-by-step.
B. Abstract Factory Pattern, because it creates families of related objects ensuring consistency.
C. Factory Pattern, because it creates simple objects based on a type parameter.
D. Singleton Pattern, because it ensures only one instance of each vehicle type.

Solution

  1. Step 1: Understand the requirement for families of related objects

    The problem states the need to create related vehicles (e.g., electric car and electric bike) that belong to a family and must be consistent.
  2. Step 2: Match pattern to requirement

    Abstract Factory is designed to create families of related objects, ensuring that the created objects are compatible and consistent.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Abstract Factory creates related object families, Factory creates single objects [OK]
Hint: Families of related objects -> Abstract Factory [OK]
Common Mistakes:
  • Confusing Factory with Abstract Factory
  • Using Builder for simple object creation
2. In which scenario is applying the Liskov Substitution Principle (LSP) most critical to ensure system correctness?
easy
A. When a subclass adds new methods without overriding any superclass methods.
B. When a subclass uses composition instead of inheritance.
C. When a subclass narrows the input parameter types of an overridden method.
D. When a subclass extends a superclass but changes the expected behavior of inherited methods.

Solution

  1. Step 1: Understand LSP's core requirement

    LSP requires that subclasses can replace their superclasses without altering desirable properties of the program, especially behavior.
  2. Step 2: Analyze each option carefully

    When a subclass extends a superclass but changes the expected behavior of inherited methods. describes a subclass changing expected behavior, which violates LSP. When a subclass adds new methods without overriding any superclass methods. is safe as adding methods doesn't break substitutability. When a subclass narrows the input parameter types of an overridden method. narrows input types (contravariance violation), which breaks substitutability but is less direct than changing behavior. When a subclass uses composition instead of inheritance. is unrelated to LSP since composition is an alternative to inheritance.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Only changing inherited method behavior breaks LSP directly.
Hint: LSP is about preserving inherited behavior, not just adding features.
Common Mistakes:
  • Thinking adding methods breaks LSP
  • Confusing covariance and contravariance in parameters
  • Assuming composition relates directly to LSP
3. You need to design a system where a fixed sequence of steps is followed to prepare different types of beverages, but some steps vary depending on the beverage type. Which design approach best ensures code reuse and enforces the sequence while allowing subclasses to customize specific steps?
easy
A. Implement separate classes with duplicated code for each beverage type.
B. Apply a greedy algorithm to select steps dynamically at runtime based on beverage type.
C. Use a base class defining the skeleton of the algorithm with abstract methods for variable steps, overridden by subclasses.
D. Use a brute force approach that tries all possible step sequences and picks the correct one.

Solution

  1. Step 1: Identify the problem structure

    The problem requires a fixed sequence of steps with some steps varying by subclass.
  2. Step 2: Match to design pattern

    The Template Method Pattern defines a skeleton algorithm in a base class and lets subclasses override specific steps, ensuring reuse and consistent sequence.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Base class controls sequence; subclasses customize steps [OK]
Hint: Fixed sequence with customizable steps -> Template Method [OK]
Common Mistakes:
  • Thinking duplicated code is acceptable for reuse
  • Confusing dynamic step selection with greedy algorithms
4. Given the following Python code using the Template Method Pattern, what is the exact output when calling Tea().prepare_recipe()?
easy
A. Boil water Steep tea bag Pour into cup
B. Boil water Pour into cup Steep tea bag
C. Steep tea bag Boil water Pour into cup
D. Boil water Steep tea bag Pour into cup Add lemon

Solution

  1. Step 1: Trace prepare_recipe steps

    Calls boil_water (prints 'Boil water'), then brew (prints 'Steep tea bag'), then pour_in_cup (prints 'Pour into cup').
  2. Step 2: Check hook method customer_wants_condiments

    Tea overrides it to return False, so add_condiments is skipped.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Condiments skipped due to hook returning False [OK]
Hint: Hook method controls optional step execution [OK]
Common Mistakes:
  • Assuming condiments always added
  • Mixing order of steps
5. Suppose you want to extend the payment system to allow switching payment strategies at runtime based on user input, including invalid or unsupported methods. Which modification best supports this requirement while maintaining clean design?
hard
A. Use a factory method to get the strategy instance and inject it into PaymentProcessor; handle invalid methods by raising exceptions.
B. Keep the strategy selection logic inside the PaymentProcessor's pay method with if-else chains.
C. Hardcode all payment methods inside PaymentProcessor and add a default fallback strategy for invalid inputs.
D. Remove the strategy interface and implement all payment methods inside PaymentProcessor with switch-case.

Solution

  1. Step 1: Understand runtime strategy switching

    Switching strategies at runtime requires decoupling strategy selection from the context and handling invalid inputs gracefully.
  2. Step 2: Identify design that supports clean extensibility and error handling

    Using a factory method to create strategy instances and injecting them into PaymentProcessor allows runtime flexibility and clean error handling via exceptions.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Factory + DI + exceptions enable runtime switching and robustness [OK]
Hint: Factory and DI enable runtime strategy switching with error handling [OK]
Common Mistakes:
  • Hardcoding strategies or using conditionals inside context