Bird
Raised Fist0
Interview Prepoop-design-patternsmediumAmazonGoogleFlipkartSwiggyCRED

Decorator Pattern - Wrapping Behaviour Without Subclassing

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

Define Abstract Component Interface Coffee

The abstract class Coffee is defined with abstract methods cost() and description(). This sets the contract for all coffee-related classes.

💡 This step establishes the interface that all concrete coffees and decorators must implement, ensuring consistent behavior.
Line:class Coffee(ABC): @abstractmethod def cost(self): pass
💡 All coffee types will share these methods, enabling decorators to wrap any Coffee object interchangeably.
📊
Decorator Pattern - Wrapping Behaviour Without Subclassing - Watch the Algorithm Execute, Step by Step
Watching this step-by-step helps you understand how decorators add behavior without subclassing, by wrapping objects and forwarding method calls.
Step 1/10
·Active fillAnswer cell
Defines the abstract component interface for the Decorator pattern.
«abstract»Coffee
+cost()
+description()
Concrete component implements the component interface.
«abstract»Coffee
+cost()
+description()
SimpleCoffee
+cost()
+description()
SimpleCoffee Coffee (1:1)
Abstract decorator holds a reference to a Coffee component and delegates calls.
«abstract»Coffee
+cost()
+description()
SimpleCoffee
+cost()
+description()
CoffeeDecorator
_coffee: Coffee
+__init__()
+cost()
+description()
SimpleCoffee Coffee (1:1)CoffeeDecorator Coffee (1:1)
Concrete decorator adds new behavior and state.
«abstract»Coffee
+cost()
+description()
SimpleCoffee
+cost()
+description()
CoffeeDecorator
_coffee: Coffee
+__init__()
+cost()
+description()
MilkDecorator
amount: int
+__init__()
+cost()
+description()
SimpleCoffee Coffee (1:1)CoffeeDecorator Coffee (1:1)MilkDecorator CoffeeDecorator (1:1)
Another concrete decorator adds behavior independently.
«abstract»Coffee
+cost()
+description()
SimpleCoffee
+cost()
+description()
CoffeeDecorator
_coffee: Coffee
+__init__()
+cost()
+description()
MilkDecorator
amount: int
+__init__()
+cost()
+description()
SugarDecorator
amount: int
+__init__()
+cost()
+description()
SimpleCoffee Coffee (1:1)CoffeeDecorator Coffee (1:1)MilkDecorator CoffeeDecorator (1:1)SugarDecorator CoffeeDecorator (1:1)
Instantiated concrete component object.
SimpleCoffee
+cost()
+description()
Decorator wraps component to add milk behavior.
MilkDecorator
_coffee: SimpleCoffee
amount: int
+cost()
+description()
SimpleCoffee
+cost()
+description()
MilkDecorator SimpleCoffee (1:1)
Stacked decorators compose behavior dynamically.
SugarDecorator
_coffee: MilkDecorator
amount: int
+cost()
+description()
MilkDecorator
_coffee: SimpleCoffee
amount: int
+cost()
+description()
SimpleCoffee
+cost()
+description()
SugarDecorator MilkDecorator (1:1)MilkDecorator SimpleCoffee (1:1)
Delegation chain for description method.
SugarDecorator
_coffee: MilkDecorator
amount: int
+description()
MilkDecorator
_coffee: SimpleCoffee
amount: int
+description()
SimpleCoffee
+description()
SugarDecorator MilkDecorator (1:1)MilkDecorator SimpleCoffee (1:1)
Delegation chain for cost method.
SugarDecorator
_coffee: MilkDecorator
amount: int
+cost()
MilkDecorator
_coffee: SimpleCoffee
amount: int
+cost()
SimpleCoffee
+cost()
SugarDecorator MilkDecorator (1:1)MilkDecorator SimpleCoffee (1:1)

Key Takeaways

Decorators wrap objects to add behavior dynamically without subclassing.

This is hard to see from code alone because the wrapping and delegation happen at runtime and are invisible in static class hierarchies.

Method calls delegate through the decorator chain, accumulating behavior step-by-step.

Visualizing each call clarifies how each decorator contributes to the final result.

Stacking multiple decorators composes complex behavior flexibly and transparently.

The trace shows how each decorator wraps the previous one, enabling modular extensions.

Practice

(1/5)
1. Consider the following code snippet implementing the Command Pattern with undo/redo stacks. After executing the commands: Add 5, Add 3, Undo, Redo, what is the final state of the receiver?
easy
A. 5
B. 8
C. 3
D. 0

Solution

  1. Step 1: Trace command executions

    Execute Add 5 -> state = 5; Execute Add 3 -> state = 8.
  2. Step 2: Trace undo and redo

    Undo removes Add 3 -> state reverts to 5; Redo reapplies Add 3 -> state returns to 8.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    State after redo is 8 [OK]
Hint: Undo then redo restores the last undone command [OK]
Common Mistakes:
  • Forgetting redo reapplies the command
  • Off-by-one in stack operations
2. Which of the following statements about handling concurrency in the Library Management System's LoanManager is INCORRECT?
medium
A. Optimistic locking is unsuitable because it always leads to deadlocks in high contention scenarios.
B. Using pessimistic locking on book records ensures no two users can borrow the same book simultaneously.
C. Implementing atomic check-and-update operations prevents race conditions during loan processing.
D. Using version numbers or timestamps can help detect conflicting updates in optimistic concurrency control.

Solution

  1. Step 1: Understand pessimistic locking

    Pessimistic locking prevents concurrent conflicting access, so Using pessimistic locking on book records ensures no two users can borrow the same book simultaneously. is correct.
  2. Step 2: Why optimistic locking is not always deadlock?

    Optimistic locking is unsuitable because it always leads to deadlocks in high contention scenarios. is incorrect; optimistic locking reduces deadlocks by retrying on conflicts.
  3. Step 3: Atomic operations prevent race conditions

    Implementing atomic check-and-update operations prevents race conditions during loan processing. is correct and essential for concurrency safety.
  4. Step 4: Versioning aids optimistic concurrency

    Using version numbers or timestamps can help detect conflicting updates in optimistic concurrency control. correctly describes conflict detection mechanisms.
  5. Final Answer:

    Option A -> Option A
  6. Quick Check:

    Optimistic locking reduces deadlocks; it does not always cause them.
Hint: Optimistic locking avoids deadlocks by retrying, pessimistic locking blocks access [OK]
Common Mistakes:
  • Believing optimistic locking always causes deadlocks
  • Confusing locking types and their effects
  • Ignoring atomicity in concurrency control
3. Examine the following buggy CompositeIterator code snippet. Which line contains the subtle bug that causes incorrect traversal order?
medium
A. Line initializing self.stack without reversing children.
B. Line checking hasNext() before popping from stack.
C. Line popping component from stack.
D. Line returning the component after processing.

Solution

  1. Step 1: Identify stack initialization issue

    Stack is initialized with children in original order, not reversed, causing traversal order reversal.
  2. Step 2: Confirm impact on traversal order

    Without reversing, popping from stack yields children in reverse order, breaking expected traversal.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Reversing children on stack initialization fixes traversal order [OK]
Hint: Stack must reverse children to preserve traversal order [OK]
Common Mistakes:
  • Forgetting to reverse children on stack push
  • Misplacing hasNext() check
4. What is the time complexity of notifying observers in the thread-safe observer pattern implementation where observers are stored in a dictionary mapping event types to sets of observers, and a snapshot list is created during notification?
medium
A. O(n) where n is total number of all observers across all event types
B. O(k) where k is the number of observers subscribed to the notified event type
C. O(k + n) where k is observers for event type and n is total observers
D. O(1) constant time due to hash set usage

Solution

  1. Step 1: Identify the data structure and notification process

    Observers are stored per event type in sets. Notification locks and copies only the observers for the specific event type.
  2. Step 2: Analyze complexity of notify()

    Notify creates a snapshot list of observers for the event type (size k) and iterates over it, so time is proportional to k, not total n.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Notify cost depends only on observers for that event type [OK]
Hint: Notify complexity depends on observers for event type only [OK]
Common Mistakes:
  • Assuming notify iterates over all observers regardless of event type
5. If a class exposes a public getter but no setter for a private field, what is a likely reason an interviewer would probe this design choice?
hard
A. To test if the candidate thinks public fields are better than getters
B. To confirm that the candidate knows getters always require setters
C. To verify that the candidate believes private fields should never be exposed
D. To check if the candidate understands immutability and controlled read-only access

Solution

  1. Step 1: Understand getter-only design

    Getter without setter implies read-only access, often for immutability or controlled exposure.
  2. Step 2: Analyze interviewer intent

    Interviewer probes if candidate recognizes design for immutability or encapsulation control.
  3. Step 3: Evaluate incorrect options

    To confirm that the candidate knows getters always require setters is false; setters are not always required. To verify that the candidate believes private fields should never be exposed is extreme; exposing read-only data is common. To test if the candidate thinks public fields are better than getters is a misconception favoring public fields.
  4. Final Answer:

    Option D -> Option D
  5. Quick Check:

    Getter-only design supports immutability and controlled access.
Hint: Getter-only means read-only, not incomplete encapsulation [OK]
Common Mistakes:
  • Assuming getters must have setters
  • Thinking private fields must never be exposed
  • Believing public fields are simpler and better