Bird
Raised Fist0
Interview Prepoop-design-patternsmediumAmazonGoogleMicrosoftFlipkartSwiggyRazorpayCRED

Observer Pattern - Event System, Publish-Subscribe

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

Create Subject instance

The Subject object is instantiated, initializing its lock and an empty dictionary to hold observers by event type.

💡 This sets up the core object that manages subscriptions and notifications, essential for the observer pattern.
Line:def __init__(self): self.lock = threading.Lock() self.observers = {}
💡 Subject starts with no observers and a lock to ensure thread safety during modifications.
📊
Observer Pattern - Event System, Publish-Subscribe - Watch the Algorithm Execute, Step by Step
Watching each step shows how observers are added, notified, and removed safely and concurrently, clarifying the pattern's dynamic behavior.
Step 1/10
·Active fillAnswer cell
Subject encapsulates observer management with thread safety.
Subject
lock: Lock
observers: Dict[str, Set[Observer]]
+__init__()
+subscribe()
+unsubscribe()
+1 more
«abstract»Observer
+update()
ConcreteObserver
name: str
+__init__()
+update()
ConcreteObserver Observer (1:1)
ConcreteObserver implements Observer interface with identity.
Subject
lock: Lock
observers: Dict[str, Set[Observer]]
+__init__()
+subscribe()
+unsubscribe()
+1 more
«abstract»Observer
+update()
ConcreteObserver
name: str
+__init__()
+update()
ConcreteObserver Observer (1:1)
Multiple ConcreteObserver instances represent different subscribers.
Subject
lock: Lock
observers: Dict[str, Set[Observer]]
+__init__()
+subscribe()
+unsubscribe()
+1 more
«abstract»Observer
+update()
ConcreteObserver
name: str
+__init__()
+update()
ConcreteObserver Observer (1:1)
Subject manages observers per event type using thread-safe locking.
Subject
lock: Lock
observers: Dict[str, Set[Observer]]
+subscribe()
ConcreteObserver
name: str
+update()
Subject supports multiple event types with separate observer sets.
Subject
lock: Lock
observers: Dict[str, Set[Observer]]
+subscribe()
ConcreteObserver
name: str
+update()
Notify uses snapshot to avoid concurrent modification issues.
Subject
lock: Lock
observers: Dict[str, Set[Observer]]
+notify()
ConcreteObserver
name: str
+update()
ConcreteObserver handles event notification in update method.
Subject
lock: Lock
observers: Dict[str, Set[Observer]]
+notify()
ConcreteObserver
name: str
+update()
Unsubscribe safely removes observers and cleans empty event keys.
Subject
lock: Lock
observers: Dict[str, Set[Observer]]
+unsubscribe()
ConcreteObserver
name: str
+update()
Notify handles missing event keys by returning empty observer lists.
Subject
lock: Lock
observers: Dict[str, Set[Observer]]
+notify()
ConcreteObserver
name: str
+update()
Selective notification based on event type subscription.
Subject
lock: Lock
observers: Dict[str, Set[Observer]]
+notify()
ConcreteObserver
name: str
+update()

Key Takeaways

Thread-safe management of observers ensures safe concurrent subscription, unsubscription, and notification.

This is hard to see from code alone because concurrency issues are subtle and not obvious without watching lock usage and snapshotting.

Observers are grouped by event type, enabling selective notification only to interested subscribers.

Visualizing the observers dictionary keyed by event type clarifies how notifications are routed.

Taking a snapshot of observers before notification prevents errors from concurrent modifications during iteration.

This step reveals why direct iteration over live collections can cause runtime errors, a detail often missed in static code reading.

Practice

(1/5)
1. When a new feature is added by extending a class hierarchy following the Open/Closed Principle, what is the typical sequence of events when the system executes a method call on the new subclass instance?
easy
A. The base class method is always executed first, then the subclass method overrides it afterward
B. The system duplicates the base class code inside the subclass to avoid modifying the base
C. The subclass method is invoked directly due to polymorphism, without modifying base class code
D. Both base and subclass methods execute sequentially because the base class is modified to call the subclass

Solution

  1. Step 1: Understand polymorphic dispatch

    Method calls on subclass instances invoke the subclass's overridden method directly.
  2. Step 2: Base class code remains unchanged

    The base class is closed for modification; no changes are made to call subclass methods explicitly.
  3. Step 3: Why other options are incorrect

    The base class method is always executed first, then the subclass method overrides it afterward incorrectly suggests base method runs first then subclass; Both base and subclass methods execute sequentially because the base class is modified to call the subclass implies base class modification; The system duplicates the base class code inside the subclass to avoid modifying the base suggests code duplication, violating DRY and OCP.
  4. Final Answer:

    Option C -> Option C
  5. Quick Check:

    Polymorphism enables extension without modifying base code.
Hint: Polymorphism calls subclass methods directly [OK]
Common Mistakes:
  • Assuming base method always runs first
  • Believing base class must be modified to support extension
  • Thinking code duplication is a valid OCP strategy
2. Imagine a class responsible for both data persistence and data validation. When a change in validation rules occurs, trace the impact on the class and explain what happens step-by-step.
easy
A. Only the validation methods need modification; persistence remains unaffected, so SRP is maintained.
B. Changing validation rules forces modifying the entire class, risking unintended side effects on persistence logic.
C. Validation changes automatically propagate to persistence without code changes due to tight coupling.
D. Persistence logic will break because validation and persistence are tightly integrated and inseparable.

Solution

  1. Step 1: Identify responsibilities

    The class handles both validation and persistence, two distinct reasons to change.
  2. Step 2: Trace change impact

    Changing validation rules requires modifying validation code inside the class.
  3. Step 3: Side effects

    Because persistence logic shares the class, changes risk affecting persistence unintentionally, increasing maintenance risk.
  4. Step 4: SRP violation

    This coupling violates SRP, as one reason to change (validation) affects unrelated functionality (persistence).
  5. Final Answer:

    Option B -> Option B
  6. Quick Check:

    One reason to change should not force changes in unrelated code -> SRP violation.
Hint: One reason to change means one place to modify.
Common Mistakes:
  • Assuming changes affect only related methods without side effects.
  • Believing tight coupling is harmless if code is in one class.
  • Thinking validation and persistence are always linked.
3. Identify the bug in the following decorator implementation that causes incorrect behavior when stacking decorators:
medium
A. Line 5: Recursive call to self.cost() instead of super().cost() causes infinite recursion
B. Line 6: Incorrect string formatting in description
C. Line 2: Incorrect call to super().__init__
D. Line 4: Missing amount parameter default value

Solution

  1. Step 1: Analyze cost() method

    cost() calls self.cost() recursively, causing infinite recursion instead of delegating to wrapped object.
  2. Step 2: Identify correct delegation

    Should call super().cost() to delegate to wrapped coffee object's cost method.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Recursive call causes stack overflow, breaking decorator chain [OK]
Hint: Decorator methods must delegate via super(), not self [OK]
Common Mistakes:
  • Calling self.cost() instead of super().cost() in decorators
  • Forgetting to delegate calls properly
4. What is the time complexity of calling the prepare_recipe method in the Template Method Pattern implementation for a beverage, assuming each step runs in constant time?
medium
A. O(1), since the number of steps is fixed and each step runs in constant time
B. O(log n), due to the hook method optimizing optional steps
C. O(n^2), because each step may call other steps recursively
D. O(n), where n is the number of steps in the recipe

Solution

  1. Step 1: Identify number of steps

    The template method defines a fixed sequence of steps (boil_water, brew, pour_in_cup, add_condiments).
  2. Step 2: Analyze step execution time

    Each step runs in constant time; the hook method only conditionally calls add_condiments but does not affect asymptotic complexity.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Fixed steps with constant time each -> O(1) total [OK]
Hint: Fixed step count -> constant time complexity [OK]
Common Mistakes:
  • Confusing n as input size
  • Assuming recursion adds complexity
5. In a legacy system, a class handles both business logic and logging. You want to refactor it following SRP, but the logging code is tightly intertwined with business logic. What is the best approach to refactor this while respecting SRP?
hard
A. Ignore SRP in this case because legacy code should not be refactored.
B. Leave logging inside the class because extracting it would break existing functionality and increase risk.
C. Merge business logic and logging into a utility class to centralize all cross-cutting concerns.
D. Extract logging into a separate class and replace logging calls with calls to this new class, even if it requires modifying many places.

Solution

  1. Step 1: Identify responsibilities

    Business logic and logging are separate reasons to change.
  2. Step 2: Refactor strategy

    Extract logging into its own class to isolate changes related to logging.
  3. Step 3: Address tight coupling

    Though intertwined, refactoring calls to the new logging class improves maintainability and respects SRP.
  4. Step 4: Evaluate other options

    Options B and D avoid refactoring, risking future issues; C increases coupling by merging unrelated concerns.
  5. Final Answer:

    Option D -> Option D
  6. Quick Check:

    Extract and isolate responsibilities even if it requires effort -> SRP compliance.
Hint: Separate concerns even if intertwined; refactor stepwise.
Common Mistakes:
  • Avoiding refactoring due to perceived risk.
  • Merging unrelated concerns for convenience.
  • Ignoring SRP in legacy code.