Bird
Raised Fist0
Interview Prepoop-design-patternshardAmazonGoogleFlipkartCREDRazorpay

Command Pattern - Undo/Redo, Request Queuing & Logging

Choose your preparation mode3 modes available
▶
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 fill★Answer 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 are designing a system where multiple unrelated classes must guarantee implementation of certain methods but share no common code. Which abstraction mechanism is most appropriate to enforce this contract?
easy
A. Use an abstract class to define the methods and provide partial implementation.
B. Use an abstract class only if all classes share a common ancestor.
C. Use a concrete class and rely on inheritance for code reuse.
D. Use an interface to declare the methods without any implementation.

Solution

  1. Step 1: Identify the need for a contract without shared code

    Interfaces define method signatures without implementation, perfect for unrelated classes needing a common contract.
  2. Step 2: Why abstract classes are less suitable here

    Abstract classes provide partial implementation and require a common ancestor, which unrelated classes lack.
  3. Step 3: Why concrete classes and inheritance don't fit

    Concrete classes imply implementation and inheritance assumes a hierarchy, which is not guaranteed.
  4. Final Answer:

    Option D -> Option D
  5. Quick Check:

    Interfaces enforce contracts without imposing inheritance or shared code.
Hint: Use interfaces when only a contract is needed, abstract classes when sharing code.
Common Mistakes:
  • Assuming abstract classes are always better for abstraction.
  • Confusing contract enforcement with code reuse.
  • Believing unrelated classes can share an abstract class.
2. You are designing a system for different types of vehicles where some can fly and some can float on water. Which design approach best supports adding new capabilities like flying or floating without modifying existing vehicle classes?
easy
A. Use inheritance to create subclasses like FlyingCar and FloatingCar from Vehicle
B. Use a single Vehicle class with flags indicating if it can fly or float
C. Use composition by creating separate capability classes like FlyBehavior and FloatBehavior and compose them with Vehicle
D. Use inheritance and override methods in each subclass to add flying or floating behavior

Solution

  1. Step 1: Identify the problem with inheritance here

    Inheritance leads to a combinatorial explosion of subclasses (FlyingCar, FloatingCar, FlyingFloatingCar), making the design rigid and hard to maintain.
  2. Step 2: Understand composition benefits

    Composition allows attaching behaviors dynamically via separate classes (FlyBehavior, FloatBehavior), promoting flexibility and easier extension.
  3. Step 3: Analyze other options

    Options A and C rely on inheritance, causing tight coupling and poor scalability. Use a single Vehicle class with flags indicating if it can fly or float uses flags, which leads to complex conditional logic and violates single responsibility.
  4. Final Answer:

    Option C -> Option C
  5. Quick Check:

    Composition supports adding new capabilities without modifying existing classes, enhancing maintainability and flexibility.
Hint: Composition lets you add capabilities by combining behaviors, inheritance forces rigid hierarchies.
Common Mistakes:
  • Assuming inheritance is always better for code reuse
  • Using flags instead of behaviors leads to messy conditionals
3. Trace the sequence of events when a player rolls the dice and lands on a ladder in a well-designed Snake and Ladder game. Which of the following best describes the correct order of internal state changes?
easy
A. Player rolls dice -> GameController calculates new position -> Board checks for ladder -> GameController updates Player position accordingly
B. Player rolls dice -> Player position updated -> Board checks for ladder -> Player position updated again if ladder found
C. Player rolls dice -> Board updates Player position directly if ladder found -> Player notified of new position
D. Player rolls dice -> Dice notifies Board -> Board updates Player position -> GameController confirms move

Solution

  1. Step 1: Dice roll triggers GameController

    Dice generates number; GameController uses it to calculate tentative position.
  2. Step 2: Board checks for ladder or snake

    GameController queries Board to see if new position has ladder or snake.
  3. Step 3: GameController updates Player position

    Based on Board's info, GameController updates Player's position accordingly.
  4. Step 4: Player position is finalized

    Player's position reflects ladder climb if applicable.
  5. Final Answer:

    Option A -> Option A
  6. Quick Check:

    Only GameController manages state changes; Board is queried but does not update Player directly.
Hint: GameController mediates all state changes after dice roll [OK]
Common Mistakes:
  • Assuming Player updates position before Board check
  • Believing Board directly updates Player position
  • Thinking Dice notifies Board
4. Which of the following statements about the Interface Segregation Principle (ISP) is INCORRECT?
medium
A. ISP aims to reduce the number of interfaces to simplify the system.
B. Clients should only depend on the interfaces that are relevant to them.
C. ISP encourages creating multiple specific interfaces rather than one general-purpose interface.
D. ISP helps avoid forcing clients to implement methods they do not use.

Solution

  1. Step 1: Recall ISP's core principle

    ISP promotes splitting interfaces so clients depend only on what they need, often increasing interface count.
  2. Step 2: Analyze options

    ISP aims to reduce the number of interfaces to simplify the system. incorrectly states ISP reduces the number of interfaces; in reality, it often increases them to avoid fat interfaces. Options A, B, and D correctly describe ISP goals.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    ISP is about interface granularity, not minimizing interface count.
Hint: ISP splits interfaces; it doesn't minimize their count.
Common Mistakes:
  • Confusing ISP with interface minimization
  • Assuming fewer interfaces always mean better design
  • Ignoring client-specific interface needs
5. What is the time complexity of processing a payment using the optimal strategy pattern implementation with dependency injection, assuming the payment method is already selected?
medium
A. O(n) where n is the number of payment methods
B. O(log n) due to searching the strategy in a factory method
C. O(1) constant time since the strategy is injected and pay() is a direct call
D. O(n^2) because of nested conditional checks inside the strategy classes

Solution

  1. Step 1: Identify the operation performed during payment

    Once the strategy is injected, calling pay() is a direct method call without iteration or recursion.
  2. Step 2: Analyze complexity of the pay() method

    The pay() method executes a simple print statement, which is O(1).
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Direct method call with no loops or recursion is constant time [OK]
Hint: Injected strategy enables direct O(1) method call [OK]
Common Mistakes:
  • Assuming factory lookup or conditionals happen at payment time
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard