7 min read
Object-Oriented Design Index
Tier 1 -- Foundations
Core Concepts
SOLID Principles
Creational Patterns
Structural Patterns
Tier 2
Tier 3
Tier 4
Tier 5
Object-Oriented Design Index
Tier 1 -- Foundations
Core Concepts
SOLID Principles
Creational Patterns
Structural Patterns
Tier 2
Tier 3
Tier 4
Tier 5
Observer Pattern
1. What Is It?
The pattern defines a one-to-many dependency between objects so that when one object (the Subject) changes , all its dependents (Observers) are notified and updated automatically.
Without it, you either poll the subject for changes (wastes resources, adds ) or hard-code update calls directly into the subject (tight , violates SRP — the subject shouldn't know who cares about its ). decouples the thing that changes from the things that react to that change.
A stock trading dashboard needs to update multiple UI widgets (a price chart, a portfolio summary, and an alerts panel) whenever a stock's price changes. A developer proposes directly calling each widget's update method from inside the stock price service whenever a new price arrives. What is the primary drawback of this approach compared to using the Observer pattern?
2. How It Works
Participants:
- Subject (also called Observable) — maintains a list of observers; provides
attach(),detach(), andnotify()operations - ConcreteSubject — stores ; calls
notify()when its changes - Observer — declaring the
update()method - ConcreteObserver — implements
update(); pulls or receives new state from the subject
Step-by-step:
- Define the
Observerwithupdate() - Define the
Subjectwithattach,detach,notify ConcreteSubjectholds state; on each state change, callsnotify()which loops over registered observers and calls theirupdate()ConcreteObserverregisters itself with the subject; onupdate(), reads new state and reacts
Push vs. Pull:
- Push model: Subject sends state data directly in the
update(event)call — observer doesn't need to query back - Pull model: Subject calls
update()with no data; observer callssubject.getState()— looser but more roundtrips
Python:
from abc import ABC, abstractmethod from dataclasses import dataclass from typing import List @dataclass class StockEvent: ticker_symbol: str price: float class StockObserver(ABC): @abstractmethod def update(self, event: StockEvent) -> None: pass class StockTicker: def __init__(self, ticker_symbol: str) -> None: self._ticker_symbol = ticker_symbol self._price: float = 0.0 self._observers: List[StockObserver] = [] def attach(self, observer: StockObserver) -> None: self._observers.append(observer) def detach(self, observer: StockObserver) -> None: self._observers.remove(observer) def set_price(self, price: float) -> None: self._price = price self._notify() def _notify(self) -> None: event = StockEvent(self._ticker_symbol, self._price) for observer in self._observers: observer.update(event) class PriceAlertObserver(StockObserver): def __init__(self, alert_threshold: float) -> None: self._alert_threshold = alert_threshold def update(self, event: StockEvent) -> None: if event.price > self._alert_threshold: print(f"ALERT: {event.ticker_symbol} hit ${event.price:.2f} " f"(threshold: ${self._alert_threshold:.2f})") class PriceDashboardObserver(StockObserver): def update(self, event: StockEvent) -> None: print(f"Dashboard updated: {event.ticker_symbol} = ${event.price:.2f}") # Usage ticker = StockTicker("AAPL") ticker.attach(PriceDashboardObserver()) ticker.attach(PriceAlertObserver(alert_threshold=180.0)) ticker.set_price(175.0) # Dashboard updated: AAPL = $175.00 ticker.set_price(185.0) # Dashboard updated: AAPL = $185.00 # ALERT: AAPL hit $185.00 (threshold: $180.00)
Java:
import java.util.ArrayList; import java.util.List; // Event data (push model) public record StockEvent(String tickerSymbol, double price) {} // Observer interface public interface StockObserver { void update(StockEvent event); } // Subject (ConcreteSubject) public class StockTicker { private final String tickerSymbol; private double price; private final List<StockObserver> observers = new ArrayList<>(); public StockTicker(String tickerSymbol) { this.tickerSymbol = tickerSymbol; } public void attach(StockObserver observer) { observers.add(observer); } public void detach(StockObserver observer) { observers.remove(observer); } public void setPrice(double price) { this.price = price; notifyObservers(); } private void notifyObservers() { StockEvent event = new StockEvent(tickerSymbol, price); for (StockObserver observer : observers) { observer.update(event); } } } // ConcreteObserver A public class PriceAlertObserver implements StockObserver { private final double alertThreshold; public PriceAlertObserver(double alertThreshold) { this.alertThreshold = alertThreshold; } @Override public void update(StockEvent event) { if (event.price() > alertThreshold) { System.out.printf("ALERT: %s hit $%.2f (threshold: $%.2f)%n", event.tickerSymbol(), event.price(), alertThreshold); } } } // ConcreteObserver B public class PriceDashboardObserver implements StockObserver { @Override public void update(StockEvent event) { System.out.printf("Dashboard updated: %s = $%.2f%n", event.tickerSymbol(), event.price()); } } // Client public class Main { public static void main(String[] args) { StockTicker ticker = new StockTicker("AAPL"); ticker.attach(new PriceDashboardObserver()); ticker.attach(new PriceAlertObserver(180.0)); ticker.setPrice(175.0); // Dashboard updated: AAPL = $175.00 ticker.setPrice(185.0); // Dashboard + Alert } }
A StockTicker subject notifies observers by calling update(StockEvent event), passing the latest price directly in the call. A junior developer suggests refactoring so that notify() calls update() with no arguments, and each observer instead calls ticker.getPrice() to retrieve the current value. What is the key trade-off of switching to this approach?
3. Variants & Comparisons
| Variant | How It Works | Pros | Cons | Best For |
|---|---|---|---|---|
| Push model | Subject sends event data in update(event) | Observer needs no back-reference to subject | Observers receive data they may not need | Event-driven systems, decoupled observers |
| Pull model | Subject calls update() with no data; observer queries subject | Observer only fetches what it needs | Observer must hold subject reference | Observers needing selective state |
| Event Bus / Mediator | Events published to a bus; observers subscribe by type | Subjects and observers never reference each other | Extra indirection, harder to trace | Large systems with many event types |
Java: java.util.Observable (deprecated in Java 9+) and java.util. were the original GoF implementation. Modern Java prefers PropertyChangeListener (from java.beans) or reactive streams (RxJava, Project Reactor). Python: Built-in signal library or observable patterns via simple lists are common; __slots__ + property setters can trigger observers cleanly.
4. When to Use It (and When NOT To)
Use when:
- Multiple components need to react to changes in another component (UI, event systems, stock feeds)
- You want to avoid polling
- The subject shouldn't know the concrete types of its observers
Don't use when:
- The chain of notifications is complex — cascading updates become hard to trace (who triggered what?)
- Observers are always the same set and won't change — just call them directly
- Notification order matters and must be guaranteed — gives no ordering guarantees
Decision triggers:
- "When X changes, Y and Z need to know about it, but X shouldn't know about Y or Z" →
- "I keep adding new
notify*()methods to a class every time a new consumer appears" → Observer
Anti-patterns:
- Memory leaks: Forgetting to
detachobservers. A subject holding strong references to observers prevents GC. Use weak references or explicit lifecycle management. - Cascading updates: Observer A's update triggers Subject B, which notifies Observer C, which loops back — hard to debug, can cause infinite loops.
A stock trading dashboard has a price feed component that updates frequently. Several UI widgets — a chart, a portfolio summary, and an alert banner — all need to refresh whenever the price changes. A developer considers using the Observer pattern. Which of the following scenarios would be the strongest reason to reconsider that choice?
5. Real-World Usage
1. Java Swing event listeners: Every Swing widget is a Subject. button.addActionListener(listener) registers an . ActionListener.actionPerformed() is update(). Swing's entire event model is .
2. Android LiveData / ViewModel: LiveData<T> is the Subject. UI controllers call liveData.observe(lifecycleOwner, observer). When data changes, all active observers are notified. The lifecycle owner ensures automatic detach on destruction — solving the memory leak anti-pattern.
3. RxJava / Project Reactor: The entire reactive programming model is Observer formalized. Observable/Flux are subjects; Observer/Subscriber receive events. They add back-pressure, threading control, and operator pipelines on top of the core pattern.
An Android app uses LiveData to expose user profile data from a ViewModel. A developer notices that in older implementations using raw Observer callbacks, Activity instances were sometimes kept alive in memory even after the user navigated away, because the Subject still held a reference to them. Which feature of Android LiveData directly addresses this memory leak problem?
6. Interview Cheat Sheet
Key sentences:
- " decouples the thing that changes (Subject) from the things that react (Observers) — the subject only knows about the , not concrete implementations."
- "Push model sends data to observers; pull model lets observers query. Push is more decoupled; pull avoids sending irrelevant data."
- "The classic pitfall is memory leaks — if you don't detach observers, the subject holds references forever, preventing garbage collection."
- "In modern Java,
PropertyChangeListenerand reactive streams (Project Reactor, RxJava) are the production-grade evolution of this pattern."
Common follow-up questions:
Q: What's the difference between Observer and Event Bus? A: Observer requires observers to register directly on the subject. An Event Bus (Mediator variant) adds a central broker — subjects and observers never reference each other, at the cost of more indirection.
Q: How do you prevent memory leaks in Observer?
A: Use weak references (Python weakref, Java WeakReference), enforce explicit detach() calls on lifecycle events, or use a framework that handles registration lifecycle (e.g., Android LiveData tied to a LifecycleOwner).
Q: What about thread safety?
A: The basic pattern isn't thread-safe. If observers are added/removed concurrently, the observer list needs synchronization (e.g., CopyOnWriteArrayList in Java for safe iteration).
Connections:
- Observer is the foundation of Model-View-Controller — Model is the Subject, Views are Observers
- Observer + Mediator → Event Bus pattern
- Observer is the imperative precursor to Reactive Streams (RxJava, Project Reactor)
Glossary History
Click dotted jargon to save explanations here.
Glossary History
Click dotted jargon to save explanations here.