Behavioral

Observer Pattern

Notify dependents automatically when one object changes state. A foundational behavioral pattern — the backbone of event-driven systems, pub/sub architectures, and reactive programming.

Overview ยท Behavioral ยท Singleton ยท Factory Method ยท Abstract Factory ยท Builder ยท Prototype ยท Adapter ยท Bridge ยท Composite ยท Decorator ยท Facade ยท Flyweight ยท Proxy ยท Observer ยท Strategy ยท Command ยท Template Method ยท Chain of Resp. ยท State ยท Mediator ยท Iterator ยท Visitor ยท Memento ยท Interpreter
Category: Behavioral Difficulty: Foundational Interview: Tier 1 Confused with: Mediator
01
Section One ยท The Problem

Why Observer Exists

You are building a stock trading platform. A StockPrice object holds the current price of a ticker symbol. Multiple parts of the system care about price changes: a charting widget needs to redraw, a portfolio tracker needs to recalculate totals, an alert service needs to check thresholds, and a logging service needs to record the history. The naive approach: the StockPrice class directly calls each dependent.

Naive approach — hard-coded notifications
// โœ— StockPrice knows every consumer by name class StockPrice { private double price; private ChartWidget chart; // hard dep #1 private PortfolioTracker portfolio; // hard dep #2 private AlertService alerts; // hard dep #3 private PriceLogger logger; // hard dep #4 public void setPrice(double p) { this.price = p; chart.redraw(price); // โ† must know Chart API portfolio.recalculate(price); // โ† must know Portfolio API alerts.checkThresholds(price); // โ† must know Alert API logger.log(price); // โ† must know Logger API } } // Add a 5th consumer? Edit StockPrice. Remove one? Edit StockPrice.

What goes wrong:

  • Tight couplingStockPrice depends on every consumer class; changing any consumer’s API forces edits to StockPrice
  • Open/Closed violation — adding or removing a consumer means editing setPrice(); the class is never closed for modification
  • No runtime flexibility — you can’t subscribe or unsubscribe a consumer at runtime (e.g. user closes the chart widget)
  • Untestable — testing StockPrice requires instantiating Chart, Portfolio, Alert, and Logger; mocking is painful
Without Observer — subject hard-wired to every consumer
StockPrice ChartWidget PortfolioTracker AlertService PriceLogger ✗ StockPrice must know every consumer ✗ Add/remove consumer โ†’ edit setPrice() ✗ No subscribe/unsubscribe at runtime ✗ Untestable โ€” requires all 4 deps

This is the problem Observer solves — define a one-to-many dependency so that when the subject (StockPrice) changes state, all registered observers are notified automatically, without the subject knowing who they are or what they do.

02
Section Two ยท The Pattern

What Is Observer?

Observer is a behavioral pattern that defines a one-to-many dependency between objects: when one object (the subject) changes state, all its dependents (the observers) are notified and updated automatically. The subject maintains a list of observers and calls a common update() method on each one whenever something interesting happens. Observers can subscribe and unsubscribe at runtime — the subject doesn’t know or care what the observers do with the notification.

GoF Intent: “Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.”
— Gamma, Helm, Johnson, Vlissides (1994)
Analogy — newspaper subscription: A newspaper publisher (the subject) doesn’t hand-deliver papers to random people. Instead, readers subscribe. When a new edition is printed, every subscriber receives a copy automatically. The publisher doesn’t know what each subscriber does with the paper — one reads the sports section, another clips coupons, a third lines a bird cage. A reader can unsubscribe at any time, and the publisher simply stops delivering. New subscribers join without the publisher changing its printing process. This is Observer: the publisher only knows that subscribers exist; it never depends on what they do.

Key insight: Observer inverts the dependency. Without the pattern, the subject calls each consumer by name (tight coupling). With Observer, the subject calls a generic interface (Observer.update()) and has no idea who is listening. The observers depend on the subject — not the other way around. This is why Observer is the foundation of every event system: DOM events, Java Swing listeners, RxJava, React state, Kafka consumers, and message queues all trace back to this pattern.

Subject holds a List<Observer> — not a list of concrete classes
Adding a new observer type requires zero changes to the subject — Open/Closed Principle
subscribe() and unsubscribe() allow runtime registration
Observers come and go dynamically — user closes a widget, it unsubscribes; user opens a new chart, it subscribes
When state changes, subject loops through the list and calls observer.update(data)
Every observer reacts independently — one redraws a chart, another sends an alert, another writes a log. The subject doesn’t know or care.
Observer depends on the subject (subscribes to it); subject depends only on the Observer interface
Loose coupling — subject and observers can be developed, tested, and deployed independently
03
Section Three ยท Anatomy

Participants & Structure

Participant Role In the Analogy
Subject (Publisher) Holds the state of interest and a list of observers. Provides subscribe(), unsubscribe(), and notifyObservers() methods. When state changes, iterates the list and calls update() on each observer. The newspaper publisher — maintains a subscriber list and distributes each new edition.
Observer (Subscriber) An interface with a single method: update(data). Every concrete observer implements this to react to state changes in its own way. The subscription contract — “you will receive a paper.”
Concrete Subject A specific implementation that holds domain state (e.g. stock price). Calls notifyObservers() whenever that state changes. The New York Times — a specific publisher with specific content.
Concrete Observer Implements update() with domain-specific logic. Knows nothing about other observers. Can subscribe/unsubscribe at any time. Individual subscribers — one reads sports, another clips coupons, a third wraps fish.
Observer — UML Class Diagram
«interface» Subject + subscribe(Observer) + unsubscribe(Observer) + notifyObservers() «interface» Observer + update(data) : void observers * StockPriceFeed - price : double + setPrice(double) ChartWidget + update(data) PortfolioTracker + update(data) AlertService + update(data) ■ Blue = interface ■ Green = concrete ◇ hollow diamond = aggregation (has-many) --▷ dashed = implements Subject knows Observer interface only — never the concrete classes
Push vs. Pull model:
  • In the push model, the subject sends the changed data inside update(data) — observers get everything they need in one call.
  • In the pull model, update() passes only a reference to the subject — observers then query the specific fields they care about.
  • Push is simpler; Pull avoids sending data observers don’t need. Both are valid Observer implementations.
04
Section Four ยท How It Works

The Pattern In Motion

Scenario: A stock price feed for AAPL. Three observers subscribe: a chart widget, a portfolio tracker, and an alert service. When the price changes, all three are notified automatically.

Step 1 — Create the subject: StockPriceFeed feed = new StockPriceFeed("AAPL")
The feed has an empty List<Observer>. No one is listening yet.
Step 2 — Observers subscribe: feed.subscribe(chart), feed.subscribe(portfolio), feed.subscribe(alert)
The feed’s observer list now has 3 entries. The feed knows nothing about these objects except that they implement Observer.
Step 3 — State changes: feed.setPrice(182.50)
setPrice() stores the new price, then calls notifyObservers(), which loops through the list and calls observer.update("AAPL", 182.50) on each.
Step 4 — Each observer reacts independently
ChartWidget redraws the candlestick. PortfolioTracker recalculates the total value. AlertService checks if the price crossed a threshold. None knows about the others.
Step 5 — User closes the chart: feed.unsubscribe(chart)
The chart is removed from the list. Next setPrice() notifies only portfolio and alert. No code changes โ€” just a runtime call.
Observer — Notification Sequence
StockPriceFeed setPrice(182.50) ChartWidget PortfolioTracker AlertService update("AAPL", 182.50) โ†’ redraws chart update("AAPL", 182.50) โ†’ recalculates total update("AAPL", 182.50) โ†’ checks threshold Subject loops List<Observer> and calls update() on each — order may vary
The pattern in pseudocode
// โ”€โ”€ Setup โ”€โ”€ StockPriceFeed feed = new StockPriceFeed("AAPL"); feed.subscribe(new ChartWidget()); feed.subscribe(new PortfolioTracker()); feed.subscribe(new AlertService(180.0)); // โ”€โ”€ Price changes โ†’ all 3 observers notified automatically โ”€โ”€ feed.setPrice(182.50); // Output: // ๐Ÿ“ˆ Chart: AAPL โ†’ $182.50 // ๐Ÿ’ผ Portfolio: AAPL now $182.50 // ๐Ÿ”” Alert: AAPL crossed $180.00! Now $182.50
Behavioral patterns need both static UML and dynamic sequence:
  • The UML (S03) shows who participates. The sequence diagram above shows who calls whom in what order.
  • For Observer, the key runtime insight is: setPrice() triggers notifyObservers(), which iterates the list and dispatches update() to each observer sequentially.
05
Section Five ยท Java Stdlib

You Already Use This

Observer is arguably the most pervasive pattern in the JDK. Every event listener, every property change notification, and every reactive stream is an Observer under the hood.

IN JAVA
Example 1 java.util.EventListener — the root marker interface for all AWT/Swing listeners. ActionListener, MouseListener, KeyListener are all Observer interfaces. A JButton (subject) holds a list of ActionListeners and calls actionPerformed(e) on each when clicked.
Example 2 java.beans.PropertyChangeListener — a generic Observer for JavaBeans. Call addPropertyChangeListener() on any bean; when a property changes, propertyChange(PropertyChangeEvent) fires on all listeners. Used heavily in Swing models and Spring framework internals.
Example 3 java.util.concurrent.Flow (Java 9+) — the reactive streams API built into the JDK. Flow.Publisher is the subject; Flow.Subscriber is the observer. Supports backpressure via request(n). This is Observer scaled for async, non-blocking systems.
Deprecated java.util.Observable / java.util.Observer — the original JDK Observer from Java 1.0. Deprecated since Java 9 because Observable is a class (not an interface), forcing single inheritance, and it isn’t thread-safe. Use PropertyChangeListener or Flow instead.
Stdlib usage — Swing ActionListener
// JButton = Subject, ActionListener = Observer JButton button = new JButton("Click me"); // Subscribe observer #1 button.addActionListener(e -> System.out.println("Observer 1: button clicked!")); // Subscribe observer #2 button.addActionListener(e -> System.out.println("Observer 2: logging click event")); // When user clicks โ†’ both listeners fire automatically
Stdlib usage — PropertyChangeListener
// Generic Observer for any bean property PropertyChangeSupport pcs = new PropertyChangeSupport(this); pcs.addPropertyChangeListener(evt -> System.out.println(evt.getPropertyName() + ": " + evt.getOldValue() + " โ†’ " + evt.getNewValue())); // Fire notification pcs.firePropertyChange("price", 150.0, 182.5); // Output: price: 150.0 โ†’ 182.5
Why java.util.Observable is deprecated:
  • It’s a class, not an interface — your subject can’t extend anything else.
  • It’s not thread-safe. Its setChanged() / notifyObservers() API is clunky.
  • Modern alternatives: PropertyChangeSupport for synchronous bean events, Flow.Publisher for reactive streams, or simply roll your own List<Observer> (it’s ~10 lines of code).
06
Section Six ยท Implementation

Build It Once

Domain: Stock Price Feed. A StockPriceFeed subject notifies subscribed observers (chart, portfolio, alert) whenever the price changes. Observers subscribe/unsubscribe at runtime.

Java — Observer Pattern Stock Feed (core)
// โ”€โ”€ Observer interface โ”€โ”€ interface PriceObserver { void update(String ticker, double price); } // โ”€โ”€ Subject โ”€โ”€ class StockPriceFeed { private final String ticker; private double price; private final List<PriceObserver> observers = new ArrayList<>(); public void subscribe(PriceObserver o) { observers.add(o); } public void unsubscribe(PriceObserver o) { observers.remove(o); } public void setPrice(double p) { this.price = p; for (PriceObserver o : observers) o.update(ticker, price); } }
ConcurrentModificationException trap:
  • Notice the notifyObservers() method iterates over new ArrayList<>(observers) — a copy of the list.
  • If you iterate the original list and an observer unsubscribes during notification, you get a ConcurrentModificationException.
  • Always iterate a snapshot when observers might unsubscribe in response to an event.
07
Section Seven ยท Watch Out

Common Mistakes

Mistake #1 — Memory leak from forgotten subscriptions: An observer subscribes but never unsubscribes. The subject holds a strong reference to it, preventing garbage collection even if the observer is no longer needed. In long-running systems (servers, desktop apps), this is the classic “lapsed listener” memory leak. Fix: Always pair subscribe() with unsubscribe() in a lifecycle callback (e.g. onClose(), dispose()). Or use WeakReference<Observer> so the GC can reclaim unused observers.
✗ Wrong — observer leaks
// โœ— Chart subscribes but never unsubscribes โ€” leaked forever void openChart() { ChartWidget chart = new ChartWidget(); feed.subscribe(chart); // user closes chart window... but chart is still in the list }
✔ Correct — unsubscribe on close
// โœ“ Unsubscribe when the observer is no longer needed void openChart() { ChartWidget chart = new ChartWidget(); feed.subscribe(chart); chart.onClose(() -> feed.unsubscribe(chart)); }
Mistake #2 — Modifying the observer list during iteration:
  • If an observer calls unsubscribe(this) inside its own update(), and the subject iterates the original list, you get a ConcurrentModificationException.
  • Fix: Iterate a snapshot: for (Observer o : new ArrayList<>(observers)), or use CopyOnWriteArrayList.
Mistake #3 — Notifying in an inconsistent state:
  • If the subject fires notifyObservers() in the middle of a multi-step state change, observers see a half-updated state.
  • Fix: Complete all state changes before calling notifyObservers(). Set all fields first, then notify once.
Mistake #4 — Heavy work inside update():
  • If an observer does expensive I/O (database write, HTTP call) inside update(), it blocks all subsequent observers.
  • The subject’s setPrice() hangs until the slowest observer finishes.
  • Fix: Keep update() lightweight — enqueue work to a background thread, or use an async Observer pattern.
08
Section Eight ยท Decision Guide

When To Use Observer

Use Observer When
  • A change in one object must trigger updates in multiple other objects, and you don’t know how many or which ones at compile time
  • You need loose coupling — the subject should not depend on the concrete observer classes
  • Observers need to subscribe and unsubscribe at runtime (e.g. UI widgets opening and closing)
  • You are building an event-driven system — GUI events, message queues, reactive streams, webhooks
  • You want to follow the Open/Closed Principle — add new observers without modifying the subject
Avoid Observer When
  • There is only one dependent — a direct method call is simpler than setting up subscribe/notify machinery
  • Observer ordering matters — Observer doesn’t guarantee notification order; use Chain of Responsibility or a pipeline instead
  • You need bidirectional communication — if objects need to talk to each other (not just listen), use Mediator
  • Notifications cascade — Observer A updates, which triggers Observer B, which triggers Observer A again — infinite loop; restructure with Mediator or event bus with deduplication
Observer vs. Confused Patterns
Pattern Communication Coupling When to pick it
Observer ← this One-to-many broadcast Subject → Observer interface only Fan-out notifications: one event, many listeners
Mediator Many-to-many via hub All objects → Mediator Complex inter-object communication (chat rooms, UI forms)
Command Request encapsulated as object Invoker → Command interface Undo/redo, queuing, logging requests
Chain of Responsibility One request, one handler in a chain Handler → next Handler Request must be handled by exactly one of many handlers
Decision Flowchart
Object state change needs to notify others? No Direct call Yes Multiple listeners? Dynamic subscribe? No Callback / Listener Yes One source broadcasts to many? No Mediator (many-to-many) Yes Observer
09
Section Nine ยท Practice

Problems To Solve

Observer problems test whether you can decouple a subject from its dependents, manage subscribe/unsubscribe lifecycles, and handle edge cases like notification ordering and concurrent modification.

Difficulty Problem Key Insight
Easy Weather Station
Build a WeatherStation subject that holds temperature, humidity, and pressure. Three displays subscribe: CurrentConditions, Statistics (running average), and Forecast (rising/falling pressure). When setMeasurements() is called, all displays update.
Tests the basic subscribe/notify loop. The interesting part: Statistics must maintain state across updates (running average), proving that each observer can have its own internal logic. Use push model — pass all 3 values in update().
Medium Event Bus with Topic Filtering
Build an EventBus where observers subscribe to specific topics (e.g. "order.created", "order.shipped") instead of receiving all events. Publishing an event with topic "order.created" notifies only subscribers of that topic.
The subject holds a Map<String, List<Observer>> instead of a flat list. subscribe(topic, observer) registers per-topic. publish(topic, data) notifies only matching observers. This is how real event buses (Guava EventBus, Spring ApplicationEvent) work under the hood.
Medium Reactive Spreadsheet Cell
Build a spreadsheet where cells observe other cells. Cell A1 holds a value. Cell B1 = A1 * 2. Cell C1 = A1 + B1. When A1 changes, B1 recalculates, which triggers C1 to recalculate. Prevent infinite loops if cells form a cycle.
Tests cascading notifications. Each cell is both a subject (notifies dependents) and an observer (recalculates when dependencies change). The hard part: cycle detection. Use a “dirty” flag or topological sort to prevent infinite re-evaluation.
Hard Thread-Safe Observer with Backpressure
Build a multi-threaded stock feed where prices arrive on a producer thread and observers run on consumer threads. If an observer is slow, the subject must not block. Implement request(n) backpressure: an observer tells the subject how many updates it can handle.
Tests async Observer + reactive streams. Use ConcurrentLinkedQueue or BlockingQueue per observer. The subject enqueues updates; each observer dequeues at its own pace. request(n) controls queue depth. This is exactly java.util.concurrent.Flow — implementing it from scratch teaches the reactive streams spec.
Interview Tip:
  • When asked about Observer, the interviewer wants to see: (1) a Subject with subscribe(), unsubscribe(), and notifyObservers(); (2) an Observer interface with update(); (3) loose coupling — the subject depends only on the observer interface; (4) runtime flexibility — observers come and go dynamically.
  • Stand-out answers mention: ConcurrentModificationException during notification (iterate a copy), memory leaks from lapsed listeners, the push vs. pull tradeoff, and how java.util.Observable was deprecated in favor of Flow.Publisher.