LESSON 6.1
Observer
Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
15 min read
Real-world analogy
Subscribing to a print magazine or a YouTube channel operates on the Observer pattern. You do not drive to the publisher's printing warehouse every morning to ask if the next issue is ready. Instead, you subscribe with your mailing address or account handle. When the publisher prints a new edition, they push a copy to every subscriber on their mailing list. When you no longer wish to receive issues, you unsubscribe, and your address is removed from the active delivery list.
The problem
You are building a cryptocurrency exchange trading dashboard. Multiple independent components must update the moment Bitcoin's price changes:
PriceTickerUI: Updates the live ticker banner on the header.OrderBookMatcher: Executes automated limit and stop-loss orders.AuditLogger: Records trade ticks to disk for regulatory compliance.
Without the Observer pattern, components must either constantly poll the market data service in an infinite CPU-intensive loop, or the market data service must hold hardcoded references to every concrete UI and audit component:
// Naive implementation: Inefficient busy-wait polling or hardcoded subject coupling
class MarketFeed {
#price = 50000;
getPrice() { return this.#price; }
}
// Client polling in busy-wait loop burns CPU and network bandwidth
function pollPrices(feed) {
let lastPrice = 0;
setInterval(() => {
const current = feed.getPrice();
if (current !== lastPrice) {
console.log(`Price changed to ${current}`);
lastPrice = current;
}
}, 50); // Polling every 50ms wastes resources and introduces latency
}public class Example {
static class MarketFeed {
private double price = 50000.0;
public double getPrice() { return price; }
}
// Polling thread wastes CPU cycles while missing transient price spikes
static class PollingClient implements Runnable {
private final MarketFeed feed;
public
Busy-polling wastes battery and CPU, while hardcoding concrete listeners inside MarketFeed violates the Open/Closed Principle whenever a new chart or trading bot is added.
The solution
The Observer pattern defines a one-to-many relationship between a publisher (Subject) and subscribers (Observers). When the subject's internal state changes, it iterates through its registered subscribers and notifies them automatically.
- Subject (
StockMarket): Maintains a collection of observers and exposes methods to attach (subscribe), detach (unsubscribe), andnotifyobservers. - Observer (
PriceObserver): An interface declaring theupdate()contract. - Concrete Observers (
TickerDisplay,TradingBot,AuditLogger): Implement theObserverinterface to react to state changes independently.
Step-by-step implementation
Step 1: Define the observer contract
The observer interface declares the update callback that subscribers implement.
class PriceObserver {
onPriceUpdate(symbol, price) {
throw new Error("onPriceUpdate() must be implemented");
}
}public class Example {
interface PriceObserver {
void onPriceUpdate(String symbol, double price);
}
Step 2: Implement the subject with subscription management
The subject stores observers in a collection and notifies them when its state mutates.
Step 3: Implement concrete observers
Each subscriber provides custom business logic upon receiving notifications.
The complete example
Here is the complete, runnable implementation showing subscription, broadcasting, and unsubscription.
--- Tick 1: Price Rises ---
[MainHeader] BTC: $65000 ▲ (+0)
--- Tick 2: Flash Crash ---
[MainHeader] BTC: $58000 ▼ (-7000)
[TradeBot] EMERGENCY: Executed STOP-LOSS sell for BTC at $58000
--- Unsubscribing Bot & Tick 3 ---
[MainHeader] BTC: $57500 ▼ (-500)Variations
Push vs pull notification models
- Push Model: The subject packages detailed change parameters into the notification payload (
update(symbol, price, volume, timestamp)). This provides high performance by eliminating extra round-trip queries, but couples the subject's broadcast payload to what specific observers might want. - Pull Model: The subject passes only a minimal notification or a reference to itself (
update(this)). Observers query the subject directly for the specific attributes they care about. This keeps the observer interface generic, but causes multiple getter round-trips per event.
The lapsed listener problem (memory leaks)
In garbage-collected environments (Java, JavaScript, C#), the Observer pattern is the single most common source of memory leaks:
- When an observer registers with a long-lived subject (such as a global singleton event bus or service), the subject retains a strong reference to the observer.
- If the observer's UI component is destroyed or unmounted, the garbage collector cannot reclaim it because the subject's subscriber list still points to it.
- Solution: Always unbind listeners (
removeEventListener, returning cleanup functions in ReactuseEffect), or use Weak References (WeakMapin JS,WeakReferencein Java).
Node.js EventEmitter and Java Flow API
- Node.js
EventEmitter: Provides an idiomatic JavaScript implementation withon(event, handler),once(event, handler), andemit(event, payload). - Java Reactive Streams (
java.util.concurrent.Flow): Introduced in Java 9, standardizing backpressure-aware Observer semantics withPublisher,Subscriber, andSubscription.
Where you've already used it
- Browser DOM:
element.addEventListener("click", handleClick)registers event observers on DOM element subjects. - Node.js
EventEmitter: Core streams (fs.createReadStream, HTTP servers) emit'data','error', and'end'events. - RxJS / ReactiveX:
Observable.subscribe()forms the foundation of reactive data streams in Angular and frontend applications. - Java
PropertyChangeListener: Standard JavaBeans event dispatching mechanism viaPropertyChangeSupport. - Spring Application Events:
@EventListenertriggers asynchronous or synchronous handling of domain events.
Pros and cons
Advantages
- Open/Closed Principle: Add new subscriber types without modifying the publisher's internal code.
- Loose Coupling: The subject only knows that subscribers implement the
Observerinterface. - Dynamic Subscriptions: Observers can subscribe and unsubscribe dynamically at runtime.
Disadvantages
- Lapsed Listener Leaks: Forgetting to unsubscribe creates silent memory leaks in long-lived applications.
- Unpredictable Update Order: Observers should never depend on a specific notification sequence.
- Cascading Updates: A notification causing an observer to update another subject can trigger endless notification loops.
When not to use it
Do not use Observer when the relationship between caller and callee is strictly one-to-one and synchronous. If an OrderService only ever needs to notify a single PaymentService, a direct method call or strategy injection is clearer than an event bus.
Related patterns
| Pattern | Relationship | Communication Direction |
|---|---|---|
| Observer | One-to-many | One-way broadcast from subject to subscribers |
| Mediator | Many-to-many | Bidirectional coordination among peer colleagues |
| Chain of Responsibility | One-to-one sequential | Passes request along a handler chain until handled |
| Command | One-to-one encapsulated | Encapsulates request as an object for queueing or undo |
Observer: event bus
Build a pub/sub event bus supporting multiple subscribers per topic, payload delivery, and dynamic unsubscription.
Click Run tests to evaluate your solution against the behavior and structural test suite.
Interview questions
What is the 'Lapsed Listener' problem and how do you resolve it?
It occurs when a long-lived subject holds a strong reference to an observer, preventing the garbage collector from reclaiming the observer when it goes out of scope. It is resolved by explicitly calling unsubscribe(), returning cleanup callbacks, or using Weak References.
What is the difference between Observer and Pub/Sub? In GoF Observer, the Subject directly holds references to its Observers and runs in the same process space. In Publish/Subscribe (e.g. Kafka, RabbitMQ), publishers and subscribers never know about each other; they communicate asynchronously through an external Message Broker or Channel.
How does backpressure relate to the Observer pattern?
In standard Observer, a fast Subject can overwhelm a slow Observer with millions of events. Backpressure (standardized in Java 9 Flow.Subscriber.request(n)) allows the observer to signal how many items it is ready to process, preventing buffer overflows.
Can an observer also act as a subject?
Yes. In reactive processing pipelines (such as RxJS or Java Stream processors), intermediate transformation operators (e.g. map, filter) act as observers to upstream producers and as subjects to downstream consumers.
Try it
In-process Event Bus LLD
Define subscription, dispatch and error semantics for a reusable event dispatcher.