Bird
Raised Fist0
Interview Prepoop-design-patternsmediumAmazonGoogleMicrosoftFlipkartSwiggyRazorpayCRED

Observer Pattern - Event System, Publish-Subscribe

Choose your preparation mode3 modes available
▶
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 fill★Answer 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. In designing a parking lot system using OOP, which component is best suited to decide which type of parking spot (e.g., compact, large, handicapped) should be allocated to an incoming vehicle?
easy
A. The ParkingLot class, as it manages all spots and vehicles
B. The ParkingSpot class, since it represents the spot's characteristics
C. The Vehicle class, because it knows its own size and type
D. A Factory or Strategy pattern component that encapsulates the allocation logic

Solution

  1. Step 1: Understand responsibilities

    The Vehicle class only knows about itself, not allocation rules. The ParkingSpot class represents a spot but doesn't decide allocation. The ParkingLot manages overall state but delegating allocation logic to a dedicated component improves modularity.
  2. Step 2: Recognize design pattern role

    The Factory or Strategy pattern encapsulates allocation logic, allowing easy extension and modification without changing core classes.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Allocation logic centralized -> easier to maintain and extend [OK]
Hint: Allocation logic belongs in a dedicated pattern component, not core entities [OK]
Common Mistakes:
  • Assigning allocation responsibility to Vehicle or ParkingSpot classes
  • Putting all logic inside ParkingLot class leading to tight coupling
2. Consider the following Python code implementing the Composite pattern with iterators. Given a composite with two leaves named 'A' and 'B', what will be the output of iterating over the composite's iterator and collecting the names in order?
easy
A. ['B', 'A']
B. ['A', 'B']
C. ['root', 'A', 'B']
D. ['A']

Solution

  1. Step 1: Trace the stack initialization

    The CompositeIterator reverses children, so stack = [leafB, leafA].
  2. Step 2: Trace iteration order

    Pop leafA first (stack now [leafB]), then leafB (stack empty). Names collected in order: 'A', then 'B'.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Reversing children in stack causes correct iteration order [OK]
Hint: Stack reversed children -> correct iteration order [OK]
Common Mistakes:
  • Assuming original order without reversing stack
  • Including composite node name in output
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. Considering extensibility in the Snake and Ladder game design, what is a key trade-off when embedding snakes and ladders directly as attributes inside the Board class versus modeling them as separate entities?
medium
A. Modeling snakes and ladders as separate entities increases runtime complexity significantly
B. Embedding snakes and ladders inside Board simplifies design but reduces flexibility to add new types of board elements later
C. Embedding snakes and ladders inside Board improves encapsulation and makes the Board immutable
D. Modeling snakes and ladders separately forces duplication of position data, increasing memory usage unnecessarily

Solution

  1. Step 1: Embedding snakes/ladders inside Board

    This approach simplifies initial design but tightly couples Board to these elements.
  2. Step 2: Impact on extensibility

    Tightly coupled design makes it harder to add new board elements (e.g., portals, traps) without modifying Board.
  3. Step 3: Modeling as separate entities

    Allows easy extension by adding new element types without changing Board internals.
  4. Step 4: Complexity and memory considerations

    Separate entities add minimal overhead; runtime complexity impact is negligible.
  5. Final Answer:

    Option B -> Option B
  6. Quick Check:

    Trade-off is between simplicity and extensibility, not runtime complexity or memory bloat.
Hint: Tight coupling simplifies now but blocks future extensions [OK]
Common Mistakes:
  • Thinking separate entities cause big runtime overhead
  • Believing embedding improves encapsulation and immutability
  • Assuming separate entities cause memory bloat
5. In a large-scale system where behaviors need to be dynamically changed at runtime, what is a key design consideration when using composition to avoid pitfalls that inheritance-based designs face?
hard
A. Use deep inheritance hierarchies to capture all behavior variations upfront.
B. Ensure that composed behavior objects are immutable to prevent side effects.
C. Avoid using interfaces and rely on concrete classes to reduce complexity.
D. Design behavior interfaces and use delegation so behaviors can be swapped without modifying the main object.

Solution

  1. Step 1: Understand dynamic behavior change

    To change behaviors at runtime, the design must support swapping behavior implementations easily.
  2. Step 2: Analyze options

    Ensure that composed behavior objects are immutable to prevent side effects. is not practical; immutability limits dynamic changes. Avoid using interfaces and rely on concrete classes to reduce complexity. increases coupling and reduces flexibility. Use deep inheritance hierarchies to capture all behavior variations upfront. contradicts favoring composition and leads to rigid designs.
  3. Step 3: Confirm best practice

    Design behavior interfaces and use delegation so behaviors can be swapped without modifying the main object. promotes defining behavior interfaces and delegating calls, enabling runtime swapping without modifying main objects.
  4. Final Answer:

    Option D -> Option D
  5. Quick Check:

    Delegation with interfaces is key to flexible, dynamic composition.
Hint: Use interfaces and delegation to swap behaviors dynamically, not inheritance or concrete classes.
Common Mistakes:
  • Thinking immutability aids dynamic behavior changes
  • Relying on inheritance for runtime flexibility
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