Chapter 05 · Article 22 of 55

Observer Pattern

Intent: Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

Article outline15 sections on this page

Overview

Intent: Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

Also Known As: Publish-Subscribe (local), Dependents, Listener

Category: Behavioural Design Pattern

The Observer pattern establishes a subscription mechanism that lets multiple objects listen to and react to events happening in the object they are observing. It is the backbone of event-driven programming and one of the most widely used patterns in software engineering - from GUI frameworks to distributed messaging systems.

The pattern separates the source of truth (Subject) from the consumers of change (Observers), enabling a clean unidirectional data flow: state changes in the subject propagate outward to all registered observers without the subject needing to know anything about them beyond a shared interface.

Core Principle: The subject never instantiates or directly depends on concrete observer classes. It holds a collection of references typed to the abstract Observer interface. This inversion of dependency is what makes the pattern powerful - the subject is closed for modification but open for extension. Any new observer type can plug in without touching a single line of subject code.

Historical Context: The Observer pattern was formalized by the Gang of Four in 1994, but the concept predates the book. Smalltalk's MVC framework (1979) used a dependency mechanism that is essentially Observer. Today, it underpins everything from JavaScript's addEventListener to Kubernetes watch APIs and Apache Kafka's consumer groups.


Problem It Solves

Consider a weather monitoring application. Multiple display units - a current-conditions display, a statistics panel, and an alert system - all need real-time weather data. Without the Observer pattern, you face two bad options:

1. Tight Coupling: The WeatherData class directly calls methods on each display. Every new display requires modifying WeatherData. This violates the Open/Closed Principle and creates a rigid, fragile system.

2. Polling Inefficiency: Each display repeatedly asks WeatherData "has anything changed?" at fixed intervals. This wastes CPU cycles, introduces latency (you only detect changes at the next poll interval), and scales poorly as the number of interested parties grows.

The Observer pattern eliminates both problems. The subject maintains a list of observers and broadcasts state changes. Observers register and deregister themselves dynamically. The subject depends only on an abstract Observer interface - it never knows the concrete types watching it.


When to Use / When NOT to Use

When to UseWhen NOT to Use
One object's state change must trigger updates in multiple other objectsOnly one object ever needs the notification
The set of dependent objects is unknown at compile time or changes at runtimeThe relationship between objects is fixed and well-known
You need loose coupling between the state producer and consumersObservers need to modify the subject's state (use Mediator instead)
Event-driven or reactive architecturesSynchronous, tightly-ordered processing is required
GUI event handling (button clicks, input changes)Simple direct method calls suffice and abstraction adds no value
Implementing MVC where Model notifies ViewsNotification order must be strictly guaranteed (Observer doesn't guarantee order)
Broadcasting configuration changes across modulesThe cost of maintaining observer lists exceeds the coupling cost

Key Concepts & Theory

Subject (Observable)

The object that holds state and sends notifications. It maintains a collection of observers and provides methods to attach, detach, and notify them. The subject is unaware of concrete observer types - it programs to the Observer interface.

Observer (Subscriber)

Any object that wants to be informed of state changes. It implements an update() method (or equivalent callback) that the subject invokes during notification.

Push vs Pull Model

  • Push Model: The subject sends detailed change data directly to observers via the update() call. Observers receive everything whether they need it or not.
  • Pull Model: The subject sends a minimal notification (often just "I changed"). Observers then query the subject for the specific data they need.

Event-Driven Architecture Connection

The Observer pattern is the local, in-process ancestor of event-driven architecture. In distributed systems, the same principle scales via message brokers (Kafka, RabbitMQ), event buses, and reactive streams. The conceptual model is identical: producers emit events, consumers subscribe and react.

Loose Coupling

The subject knows only that its observers implement a certain interface. It doesn't know their concrete class, what they do with the data, or any other detail. This means:

  • Subject and observers can vary independently
  • New observers can be added without modifying the subject
  • Observers can be reused across different subjects

ASCII Class Diagram

┌─────────────────────────────┐         ┌──────────────────────────┐
│        «interface»          │         │      «interface»         │
│          Subject            │         │        Observer          │
├─────────────────────────────┤         ├──────────────────────────┤
│ + attach(observer: Observer)│◇───────▶│ + update(data): void     │
│ + detach(observer: Observer)│         └──────────────────────────┘
│ + notify(): void            │                    ▲
└─────────────────────────────┘                    │
              ▲                                    │
              │                       ┌────────────┼────────────┐
              │                       │            │            │
┌─────────────┴───────────────┐  ┌────┴─────┐ ┌───┴──────┐ ┌──┴──────────┐
│      ConcreteSubject        │  │DisplayUnit│ │Statistics │ │AlertSystem  │
├─────────────────────────────┤  │  Display  │ │ Display  │ │             │
│ - state: State              │  ├──────────┤ ├──────────┤ ├─────────────┤
│ - observers: List<Observer> │  │+update() │ │+update() │ │+update()    │
├─────────────────────────────┤  └──────────┘ └──────────┘ └─────────────┘
│ + getState(): State         │
│ + setState(s: State): void  │
│ + attach(o): void           │
│ + detach(o): void           │
│ + notify(): void            │
└─────────────────────────────┘

Interaction flow:

Client ──▶ ConcreteSubject.setState()
                  │
                  ▼
           notify() iterates observers[]
                  │
        ┌─────────┼─────────┐
        ▼         ▼         ▼
   observer1  observer2  observer3
    .update()  .update()  .update()

Pseudocode Implementation

Weather Station Example (Push Model)

// ─── Observer Interface ───
interface Observer:
    method update(temperature: float, humidity: float, pressure: float)

// ─── Subject ───
class WeatherData:
    private observers: List<Observer> = []
    private temperature: float
    private humidity: float
    private pressure: float

    method attach(observer: Observer):
        observers.add(observer)

    method detach(observer: Observer):
        observers.remove(observer)

    method notify():
        for each observer in observers:
            observer.update(temperature, humidity, pressure)

    method setMeasurements(temp: float, hum: float, pres: float):
        temperature = temp
        humidity = hum
        pressure = pres
        notify()  // automatically broadcast on state change

// ─── Concrete Observers ───
class DisplayUnit implements Observer:
    method update(temperature, humidity, pressure):
        print("Current: " + temperature + "°C, " + humidity + "% humidity")

class StatisticsDisplay implements Observer:
    private readings: List<float> = []

    method update(temperature, humidity, pressure):
        readings.add(temperature)
        print("Avg Temp: " + average(readings))

class AlertSystem implements Observer:
    method update(temperature, humidity, pressure):
        if temperature > 40:
            triggerAlert("HEAT WARNING: " + temperature + "°C")

// ─── Client Usage ───
weatherData = new WeatherData()

display = new DisplayUnit()
stats = new StatisticsDisplay()
alerts = new AlertSystem()

// Registration
weatherData.attach(display)
weatherData.attach(stats)
weatherData.attach(alerts)

weatherData.setMeasurements(32.5, 65.0, 1013.2)
// All three observers receive the update

// Unregistration
weatherData.detach(stats)

weatherData.setMeasurements(42.0, 30.0, 1008.5)
// Only display and alerts receive this update

Event System Variant (Typed Events)

// ─── Typed Event System ───
class Event<T>:
    private source: Object
    private data: T
    private timestamp: DateTime

class EventBus:
    private subscribers: Map<EventType, List<Handler>> = {}

    method subscribe<T>(eventType: Class<T>, handler: Handler<T>):
        if eventType not in subscribers:
            subscribers[eventType] = []
        subscribers[eventType].add(handler)

    method unsubscribe<T>(eventType: Class<T>, handler: Handler<T>):
        subscribers[eventType].remove(handler)

    method publish<T>(event: Event<T>):
        eventType = event.class
        if eventType in subscribers:
            for each handler in subscribers[eventType]:
                handler.handle(event)

// ─── Typed Events ───
class TemperatureChangedEvent extends Event<float>
class HumidityChangedEvent extends Event<float>
class PressureChangedEvent extends Event<float>

// ─── Usage ───
bus = new EventBus()

bus.subscribe(TemperatureChangedEvent, (event) ->
    print("Temperature changed to: " + event.data)
)

bus.subscribe(TemperatureChangedEvent, (event) ->
    if event.data > 40: triggerAlert("HEAT WARNING")
)

// Only temperature subscribers are notified
bus.publish(new TemperatureChangedEvent(source=weatherStation, data=42.0))

Push vs Pull Model

AspectPush ModelPull Model
Data flowSubject sends all data to observerSubject sends minimal signal; observer queries back
CouplingObserver's update signature coupled to subject's data shapeObserver coupled to subject's getter interface
EfficiencyMay send unnecessary dataObserver fetches only what it needs
SimplicitySimpler for observers (data arrives ready)Simpler for subject (just notifies)
Use whenAll observers need the same dataObservers need different subsets of data

Push Model Snippet

// Subject pushes all data
method notify():
    for observer in observers:
        observer.update(temperature, humidity, pressure)

// Observer receives everything
method update(temp, humidity, pressure):
    this.display(temp)  // ignores humidity and pressure

Pull Model Snippet

// Subject sends reference to itself
method notify():
    for observer in observers:
        observer.update(this)

// Observer pulls only what it needs
method update(subject: WeatherData):
    temp = subject.getTemperature()  // pulls only temperature
    this.display(temp)

Real-World Examples

1. GUI Event Listeners

Every modern UI framework uses Observer. A button maintains a list of click listeners. When clicked, it notifies all registered handlers. DOM addEventListener, Java Swing ActionListener, and Android OnClickListener are all Observer implementations. The browser's entire event model - bubbling, capturing, delegation - is built on layered Observer relationships where DOM nodes are subjects and event handlers are observers.

2. Pub/Sub Systems

Message brokers like Kafka, RabbitMQ, and Redis Pub/Sub extend the Observer pattern to distributed systems. Topics are subjects, consumers are observers, and the broker manages subscriptions. The key difference from in-process Observer is that the broker decouples producers and consumers in both space (different machines) and time (messages persist until consumed).

3. MVC Pattern

In Model-View-Controller, the Model is the subject and Views are observers. When model data changes, all views reflecting that data update automatically. This is the foundation of frameworks like Angular, React (via state/context), and Spring MVC. React's useState hook and context API are modern incarnations - when state changes, all components consuming that state re-render.

4. Reactive Streams

RxJava, RxJS, Project Reactor, and Kotlin Flow are all built on the Observer pattern. An Observable emits items, and Subscribers react to them. They add backpressure, error handling, and composition operators on top of the basic pattern. The Reactive Streams specification standardizes this with four interfaces: Publisher, Subscriber, Subscription, and Processor.

5. Stock Price Tickers

A stock exchange broadcasts price changes. Trading algorithms, portfolio dashboards, and alert systems all subscribe to price feeds. Each processes the same event differently - one displays, one calculates, one triggers trades. Financial systems often use multicast UDP or dedicated market data protocols that are essentially hardware-optimized Observer implementations.

6. Social Media Notifications

When a user posts content, all followers receive notifications. The user (subject) doesn't know or care how many followers exist or what they do with the notification. Followers subscribe/unsubscribe dynamically. At scale (millions of followers), this becomes a fan-out problem solved by async notification queues - but the conceptual model remains Observer.


Observer vs Pub/Sub vs Event Bus vs Mediator

AspectObserverPub/SubEvent BusMediator
CouplingSubject knows observers exist (holds references)Publisher and subscriber are fully decoupledComponents know only the busComponents know only the mediator
CommunicationDirect (subject → observer)Indirect via broker/channelIndirect via shared busIndirect via mediator object
ScopeIn-process, localCan be distributed (cross-process, cross-network)Typically in-processIn-process
TopologyOne-to-many (single subject)Many-to-many (multiple topics)Many-to-manyMany-to-many (star topology)
FilteringObserver receives all notifications from its subjectSubscribers filter by topic/channelSubscribers filter by event typeMediator routes selectively
Subject awarenessSubject maintains observer listPublisher doesn't know subscribersPublisher doesn't know subscribersColleagues don't know each other
Use caseUI components, local stateMicroservices, distributed eventsApplication-wide eventsComplex object interactions (chat rooms, air traffic control)

Advantages & Disadvantages

AdvantagesDisadvantages
Loose coupling between subject and observersCan cause unexpected cascading updates
Open/Closed Principle - add observers without modifying subjectObservers notified in undefined order
Supports broadcast communicationMemory leaks if observers aren't properly deregistered
Dynamic relationships - subscribe/unsubscribe at runtimeDebugging difficulty - hard to trace notification chains
Foundation for reactive and event-driven systemsPerformance overhead with large observer lists
Subject and observers can be reused independentlyRisk of infinite loops if observer modifies subject
Enables separation of concernsPush model may send unnecessary data to observers

Constraints & Edge Cases

Memory Leaks (Lapsed Listener Problem)

If an observer is no longer needed but never calls detach(), the subject holds a reference to it, preventing garbage collection. This is one of the most common bugs in Observer implementations. In long-running applications (servers, desktop apps), leaked observers accumulate over time, consuming memory and causing phantom notifications to dead objects.

Mitigation: Use weak references, implement dispose() patterns, or use subscription tokens that auto-expire. In languages with GC, WeakReference<Observer> allows collection of abandoned observers. Modern frameworks like RxJava provide CompositeDisposable to manage observer lifecycles cleanly.

Notification Order

The Observer pattern does not guarantee the order in which observers are notified. If observer B depends on observer A having already processed the update, the pattern breaks. This is particularly dangerous when observers have side effects that create implicit dependencies between them.

Mitigation: If order matters, use a priority queue or Chain of Responsibility instead. Alternatively, document and enforce registration order as a contract. Some implementations support priority-based notification where observers declare their execution priority.

Cascading Updates

Observer A receives a notification and modifies the subject (or another subject), triggering another round of notifications. This can cause infinite loops or stack overflows. Even without infinite loops, cascading updates make the system's behavior unpredictable and extremely difficult to debug.

Mitigation: Use a "dirty flag" or "updating" boolean to suppress re-entrant notifications. Batch changes and notify once at the end. Some frameworks use microtask queues (like JavaScript's event loop) to defer cascading notifications to the next tick.

Thread Safety

In concurrent systems, the observer list can be modified (attach/detach) while notify() is iterating over it, causing ConcurrentModificationException or missed notifications.

Mitigation: Use copy-on-write lists, synchronize access, or iterate over a snapshot of the observer list. In Java, CopyOnWriteArrayList is purpose-built for this.

Observer Throwing Exceptions

If one observer's update() throws an exception, subsequent observers in the notification loop may never be called.

Mitigation: Wrap each update() call in a try-catch within the notify() loop. Log the error and continue notifying remaining observers. Never let one faulty observer break the entire notification chain.


Interview Follow-ups

Q1: How would you implement the Observer pattern in a thread-safe manner?

Answer: Use a CopyOnWriteArrayList (or equivalent) for the observer collection so that iteration during notify() is safe even if attach()/detach() is called concurrently. Alternatively, take a snapshot (defensive copy) of the observer list at the start of notify() and iterate over the snapshot. For the subject's state, use atomic variables or synchronize setState() and getState(). In distributed systems, use message queues which handle concurrency inherently.

Q2: What is the Lapsed Listener problem and how do you solve it?

Answer: The Lapsed Listener problem occurs when an observer is no longer logically active but remains registered with the subject. The subject holds a strong reference, preventing garbage collection, causing memory leaks. Solutions include: (1) weak references so GC can collect unused observers, (2) explicit lifecycle management with dispose() methods, (3) subscription tokens with auto-unsubscribe on scope exit (like Rx's Disposable), and (4) time-based expiry for inactive observers.

Q3: How does the Observer pattern relate to Reactive Programming?

Answer: Reactive Programming (Rx) is an evolution of the Observer pattern. An Rx Observable is the subject, and Subscribers are observers. Rx adds: (1) composition operators (map, filter, flatMap), (2) error channels (onError), (3) completion signals (onComplete), (4) backpressure for flow control, and (5) schedulers for threading. The Reactive Streams specification (java.util.concurrent.Flow) formalizes this with Publisher, Subscriber, Subscription, and Processor interfaces.

Q4 (Hint Only): Design a notification system where observers can specify which events they care about (selective subscription).

Hint: Think about combining the Observer pattern with a filtering mechanism. Consider using event types as keys in a map, where each key maps to a list of interested observers. This is essentially the typed event bus approach - subscribers register for specific event classes rather than all events from a subject.

Q5 (Hint Only): How would you handle the case where notification must be guaranteed even if the application crashes mid-notification?

Hint: Consider persistent event storage. Think about event sourcing - write the event to a durable log (database, Kafka) before notifying. On recovery, replay unacknowledged events. This moves from in-memory Observer to a persistent pub/sub model with at-least-once delivery guarantees.


Counter Questions to Ask Interviewer

  1. "Is this in-process or distributed?" - Determines whether you need a local Observer or a message broker (Kafka/RabbitMQ). The implementation complexity differs dramatically.

  2. "Do observers need guaranteed delivery, or is best-effort acceptable?" - Drives the choice between synchronous in-memory notification vs. persistent queues with acknowledgment.

  3. "Can observers be slow? Is backpressure a concern?" - If yes, you may need async notification, buffering, or reactive streams rather than synchronous Observer.

  4. "Is notification order significant?" - If yes, the basic Observer pattern may not suffice; you might need ordered event processing or a priority-based dispatch.

  5. "What's the expected observer count - tens or millions?" - At scale, maintaining per-subject observer lists becomes impractical. You'd shift to topic-based pub/sub with partitioning.

  6. "Should observers be notified synchronously or asynchronously?" - Synchronous is simpler but blocks the subject. Async decouples timing but adds complexity (threading, ordering, error handling).


References & Whitepapers

  1. Gang of Four (GoF) - Gamma, Helm, Johnson, Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software (1994). Chapter on Observer Pattern, pp. 293-303.

  2. The Reactive Manifesto - Jonas Bonér, Dave Farley, Roland Kuhn, Martin Thompson. reactivemanifesto.org. Defines responsive, resilient, elastic, and message-driven systems built on Observer principles.

  3. ReactiveX Documentation - reactivex.io. Comprehensive guide to Observable sequences, operators, and schedulers across languages (RxJava, RxJS, RxSwift, etc.).

  4. Reactive Streams Specification - reactive-streams.org. JVM standard for asynchronous stream processing with non-blocking backpressure (java.util.concurrent.Flow in Java 9+).

  5. Martin Fowler - Event-Driven Architecture - martinfowler.com. Distinguishes event notification, event-carried state transfer, event sourcing, and CQRS.

  6. Head First Design Patterns - Freeman & Robson. Chapter 2: The Observer Pattern - "Keeping your objects in the know."


  • Strategy Pattern - Often combined with Observer; observers may use different strategies to process notifications
  • Mediator Pattern - Centralizes communication instead of direct subject-observer relationships
  • Command Pattern - Commands can be queued and replayed; useful for undo in event-driven systems
  • State Pattern - Subject's state transitions can trigger observer notifications
  • Iterator Pattern - Used internally to traverse observer collections during notification
  • Chain of Responsibility - Alternative when notification order and filtering matter
  • Event Sourcing - Distributed evolution of Observer with persistent event logs
  • Pub/Sub Architecture - Decoupled, distributed Observer with message brokers