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 Use | When NOT to Use |
|---|---|
| One object's state change must trigger updates in multiple other objects | Only one object ever needs the notification |
| The set of dependent objects is unknown at compile time or changes at runtime | The relationship between objects is fixed and well-known |
| You need loose coupling between the state producer and consumers | Observers need to modify the subject's state (use Mediator instead) |
| Event-driven or reactive architectures | Synchronous, 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 Views | Notification order must be strictly guaranteed (Observer doesn't guarantee order) |
| Broadcasting configuration changes across modules | The 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
| Aspect | Push Model | Pull Model |
|---|---|---|
| Data flow | Subject sends all data to observer | Subject sends minimal signal; observer queries back |
| Coupling | Observer's update signature coupled to subject's data shape | Observer coupled to subject's getter interface |
| Efficiency | May send unnecessary data | Observer fetches only what it needs |
| Simplicity | Simpler for observers (data arrives ready) | Simpler for subject (just notifies) |
| Use when | All observers need the same data | Observers 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
| Aspect | Observer | Pub/Sub | Event Bus | Mediator |
|---|---|---|---|---|
| Coupling | Subject knows observers exist (holds references) | Publisher and subscriber are fully decoupled | Components know only the bus | Components know only the mediator |
| Communication | Direct (subject → observer) | Indirect via broker/channel | Indirect via shared bus | Indirect via mediator object |
| Scope | In-process, local | Can be distributed (cross-process, cross-network) | Typically in-process | In-process |
| Topology | One-to-many (single subject) | Many-to-many (multiple topics) | Many-to-many | Many-to-many (star topology) |
| Filtering | Observer receives all notifications from its subject | Subscribers filter by topic/channel | Subscribers filter by event type | Mediator routes selectively |
| Subject awareness | Subject maintains observer list | Publisher doesn't know subscribers | Publisher doesn't know subscribers | Colleagues don't know each other |
| Use case | UI components, local state | Microservices, distributed events | Application-wide events | Complex object interactions (chat rooms, air traffic control) |
Advantages & Disadvantages
| Advantages | Disadvantages |
|---|---|
| Loose coupling between subject and observers | Can cause unexpected cascading updates |
| Open/Closed Principle - add observers without modifying subject | Observers notified in undefined order |
| Supports broadcast communication | Memory leaks if observers aren't properly deregistered |
| Dynamic relationships - subscribe/unsubscribe at runtime | Debugging difficulty - hard to trace notification chains |
| Foundation for reactive and event-driven systems | Performance overhead with large observer lists |
| Subject and observers can be reused independently | Risk of infinite loops if observer modifies subject |
| Enables separation of concerns | Push 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
-
"Is this in-process or distributed?" - Determines whether you need a local Observer or a message broker (Kafka/RabbitMQ). The implementation complexity differs dramatically.
-
"Do observers need guaranteed delivery, or is best-effort acceptable?" - Drives the choice between synchronous in-memory notification vs. persistent queues with acknowledgment.
-
"Can observers be slow? Is backpressure a concern?" - If yes, you may need async notification, buffering, or reactive streams rather than synchronous Observer.
-
"Is notification order significant?" - If yes, the basic Observer pattern may not suffice; you might need ordered event processing or a priority-based dispatch.
-
"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.
-
"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
-
Gang of Four (GoF) - Gamma, Helm, Johnson, Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software (1994). Chapter on Observer Pattern, pp. 293-303.
-
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.
-
ReactiveX Documentation - reactivex.io. Comprehensive guide to Observable sequences, operators, and schedulers across languages (RxJava, RxJS, RxSwift, etc.).
-
Reactive Streams Specification - reactive-streams.org. JVM standard for asynchronous stream processing with non-blocking backpressure (java.util.concurrent.Flow in Java 9+).
-
Martin Fowler - Event-Driven Architecture - martinfowler.com. Distinguishes event notification, event-carried state transfer, event sourcing, and CQRS.
-
Head First Design Patterns - Freeman & Robson. Chapter 2: The Observer Pattern - "Keeping your objects in the know."
Related Topics
- 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