Bird
Raised Fist0

Observer Pattern - Event-Driven Design and Decoupling Publisher from Subscriber

LLDeasyInterview freq: high
Choose your preparation mode4 modes available
🏗️
Observer Pattern - Event-Driven Design and Decoupling Publisher from Subscriber
Decouple event publishers from subscribers using Observer pattern for scalable event-driven systems.
easyDesign Patterns - AppliedFreq: high
Prerequisites:OOP BasicsEvent-Driven Design
⚙️
Functional Requirements
  • Subject must allow observers to register and unregister dynamically.
  • Subject must notify all registered observers when an event occurs.
  • Observers must implement a callback method to receive event updates.
  • Notification must be thread-safe to handle concurrent modifications.
  • Observers should not block the subject's event dispatching.
  • Subject should maintain a consistent state of observers during notification.
  • Support multiple event types with type-safe event handling.
👤
Use Cases
  • Client registers multiple observers to a subject to receive event notifications.
  • Subject notifies all registered observers asynchronously when an event occurs.
  • Observers can unsubscribe dynamically from the subject.
  • Multiple subjects can exist independently, each managing its own observers.
  • Observers can subscribe to multiple subjects.
  • System handles concurrent event notifications safely.
🧑‍💻
Actors
  • Subject (Publisher)
  • Observer (Subscriber)
  • Event
  • Client
❓
Clarifying Questions to Ask
  • How many threads will concurrently modify the observer list (register/unregister)?
  • Should event notifications be synchronous or asynchronous?
  • Is there a pricing or resource cost associated with event dispatching?
  • What happens if an observer throws an exception during notification?
  • Are there multiple event types or just a single event type?
  • Should observers be weakly referenced to avoid memory leaks?
  • Is there a maximum number of observers per subject?
Out of Scope
✗ Event queue persistence and replay.
✗ Distributed event dispatching across network.
✗ Complex event filtering or prioritization.
✗ Observer execution order guarantees.
⚡
QPS Calculation
write qps1000
read qps5000
peak qps6000
🖥️
Server Estimate
3
Estimates based on expected number of events and observers, ensuring system scales with concurrency.
🔢
Enumerations
enum EventType
DATA_CHANGED | USER_LOGGED_IN | ERROR_OCCURRED | CONNECTION_CLOSED
Defines event categories to enable type-safe event dispatching and prevent typo bugs in event handling.
enum NotificationStatus
SUCCESS | FAILURE | PENDING
Represents the result of notifying an observer, enabling retry or error handling logic.
📦
Subject
2 fields
private final List<Observer> observersprivate final Object lock
Manage observer registration/unregistration and notify all observers of events in a thread-safe manner.
Why thisUsing Java synchronized blocks with a private lock object ensures thread safety without external dependencies.
Why notAvoided using java.util.concurrent.CopyOnWriteArrayList due to higher memory overhead and slower writes.
public void registerObserver(Observer observer)Adds an observer to the list in a thread-safe manner.
public void unregisterObserver(Observer observer)Removes an observer from the list in a thread-safe manner.
public void notifyObservers(Event event)Notifies all registered observers of the event, ensuring thread-safe iteration.
📦
Observer
0 fields
Defines the interface for objects that receive event notifications from subjects.
Why thisInterface abstraction allows multiple implementations and loose coupling.
Why notAvoided abstract classes to allow multiple inheritance of interfaces.
public void update(Event event)Callback method invoked by Subject to notify about an event.
📦
Event
2 fields
private final EventType typeprivate final Object data
Encapsulates event information including type and associated data.
Why thisImmutable event objects ensure thread safety and consistency.
Why notAvoided mutable events to prevent synchronization complexity.
public EventType getType()Returns the event type.
public Object getData()Returns the event payload.
📦
ConcreteSubject
1 fields
private volatile boolean changed
Concrete implementation of Subject that tracks state changes and triggers notifications.
Why thisVolatile flag with synchronized setters ensures visibility and atomicity of state changes.
Why notAvoided AtomicBoolean to reduce dependency and complexity.
protected synchronized void setChanged()Marks the subject as changed in a thread-safe manner.
protected synchronized void clearChanged()Clears the changed flag.
public synchronized boolean hasChanged()Checks if the subject state has changed.
public void notifyObservers(Event event)Overrides to notify observers only if changed flag is set.
📦
ConcreteObserver
1 fields
private final String observerId
Concrete implementation of Observer that processes received events.
Why thisUnique observerId helps in managing and identifying observers.
Why notAvoided anonymous observers to improve traceability.
public void update(Event event)Handles event notification with custom logic.
🔗
Relationships
Subject◇──►Observer[1..*]
aggregationObservers can exist independently of Subject
ConcreteSubject──►Subject[1..1]
inheritanceConcreteSubject is a subtype of Subject
ConcreteObserver──►Observer[1..1]
inheritanceConcreteObserver is a subtype of Observer
Subject──►Event[1..*]
associationEvent objects are created externally and passed to Subject
Observer──►Event[1..1]
associationObserver receives Event instances during update calls
🗄️
Observers
PK: subjectId
subjectId UUIDobserverId UUIDobserverType VARCHARcreatedAt TIMESTAMP
Stores observers registered to each subject for efficient lookup.
🗄️
Events
PK: eventType
eventId UUIDeventType VARCHAReventTimestamp TIMESTAMPpayload BLOB
Stores events emitted by subjects for auditing and replay.
🔌
RegisterObserver
Request
{
  "subjectId": "UUID",
  "observerId": "UUID",
  "observerType": "String"
}
Response
{
  "status": "SUCCESS|FAILURE",
  "message": "string"
}
Registers an observer to a subject.
🔌
UnregisterObserver
Request
{
  "subjectId": "UUID",
  "observerId": "UUID"
}
Response
{
  "status": "SUCCESS|FAILURE",
  "message": "string"
}
Unregisters an observer from a subject.
🔌
NotifyEvent
Request
{
  "subjectId": "UUID",
  "eventType": "String",
  "payload": "Object"
}
Response
{
  "status": "SUCCESS|FAILURE",
  "message": "string"
}
Triggers event notification to all registered observers.

Practice

(1/5)
1. A class has a private constructor and a static synchronized getInstance() method returning a single instance. Which pattern is applied and why is this the CORRECT use case?
easy
A. Factory Method pattern, because it controls object creation with a static method.
B. Singleton pattern, ensuring only one instance exists with thread-safe lazy initialization.
C. Builder pattern, to construct complex objects step-by-step with controlled instantiation.
D. Prototype pattern, to clone existing instances instead of creating new ones.

Solution

  1. Step 1: Identify the pattern by constructor and method signature

    The private constructor prevents external instantiation, and the static synchronized getInstance() method returns a single shared instance, matching Singleton semantics.
  2. Step 2: Explain why Singleton is correct here

    Singleton ensures a single instance with thread-safe lazy initialization via synchronization, which fits the described code. Factory Method and Builder patterns do not restrict instance count, and Prototype involves cloning, not single instance control.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Private constructor + synchronized static getInstance() -> Singleton pattern [check]
Hint: Private constructor + static synchronized getInstance() -> Singleton [check]
Common Mistakes:
2. A class has a private constructor and a static synchronized getInstance() method returning the single instance. Which pattern is applied and why is this the CORRECT use case?
easy
A. Singleton pattern, because it ensures only one instance exists and controls concurrent access.
B. Factory Method pattern, because it controls object creation with a static method.
C. Builder pattern, because it constructs complex objects step-by-step.
D. Prototype pattern, because it clones existing instances to avoid new construction.

Solution

  1. Step 1: Identify Singleton pattern characteristics

    The private constructor prevents external instantiation, and the static synchronized getInstance() method ensures a single instance with thread-safe lazy initialization.
  2. Step 2: Eliminate other creational patterns

    Factory Method controls subclass instantiation, Builder separates construction from representation, and Prototype clones objects; none enforce single instance or thread safety.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Private constructor + synchronized static getInstance() -> Singleton pattern [check]
Hint: Private constructor + synchronized getInstance() -> Singleton [check]
Common Mistakes:
3. In a parking system, parkVehicle() checks if a spot is available and then marks it occupied. Without synchronized, which exact thread interleaving causes double-booking?
medium
A. Thread 1 marks spot occupied, Thread 2 waits until spot is free, then parks
B. Thread 1 checks isAvailable() -> true, context switch before marking occupied; Thread 2 checks isAvailable() -> true, both mark occupied
C. Thread 1 parks vehicle, Thread 2 parks vehicle after Thread 1 completes
D. Thread 1 and Thread 2 both wait on a lock, preventing double-booking

Solution

  1. Step 1: Identify the check-then-act race condition

    Without synchronization, Thread 1 reads spot as available but is preempted before marking it occupied. Thread 2 also reads available and proceeds to mark occupied.
  2. Step 2: Confirm double-booking scenario

    Both threads believe the spot is free and mark it occupied, causing double-booking due to the race between check and set.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Check-then-act gap without synchronization -> double-booking [check]
Hint: Check-then-act without lock -> race condition double-booking [check]
Common Mistakes:
4. To support ElectricCar jobs requiring ChargingSpot resources in the Job Scheduler, what is the minimum change to the current design to maintain extensibility without modifying existing classes?
hard
A. Create a new GodClass managing both ElectricCar and ChargingSpot logic, bypassing existing abstractions.
B. Add ELECTRIC_CAR to VehicleType enum, CHARGING to SpotType enum, create ElectricCar subclass, and update SpotCompatibility map externally.
C. Modify VehicleType enum to include ElectricCar and add charging logic inside existing Vehicle class.
D. Add charging logic directly inside Job class and modify existing Spot classes to handle ElectricCar.

Solution

  1. Step 1: Identify extensibility requirements

    ElectricCar and ChargingSpot are new types requiring minimal impact on existing Vehicle and Spot classes.
  2. Step 2: Apply extensibility without modifying existing classes

    Add new enum values for types, create ElectricCar subclass, and update SpotCompatibility map externally to maintain OCP and avoid class explosion.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    New enums + subclass + external compatibility update -> minimal existing code change [check]
Hint: Add new enums + subclass + external map update -> minimal change [check]
Common Mistakes:
5. ElectricCar needs to be added to the product catalog with ChargingSpots. What is the minimum change to support this without modifying existing classes?
hard
A. Create a new ElectricCar class and a new ChargingSpot class, modifying Vehicle and Spot base classes to support them
B. Add ELECTRIC_CAR to VehicleType enum and CHARGING to SpotType enum, create ElectricCar extends Vehicle, update SpotCompatibility.COMPATIBILITY_MAP accordingly
C. Modify Vehicle class to add isElectric flag and add charging logic inside existing ParkingSpot classes
D. Add charging logic inside Vehicle class and extend SpotType enum with charging spots, modifying existing compatibility logic

Solution

  1. Step 1: Identify extensibility without modifying existing classes

    Adding new enum values and new classes for ElectricCar and ChargingSpot allows extension without changing existing class code, preserving OCP.
  2. Step 2: Why updating compatibility map is needed

    SpotCompatibility.COMPATIBILITY_MAP controls which vehicles fit which spots; updating it ensures ElectricCar and ChargingSpot compatibility without modifying base classes.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    New enums + classes + compatibility map update -> minimal existing class changes [check]
Hint: Add enums + classes + map, no base class changes [check]
Common Mistakes:
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