Tier 1 · Foundations

Core Concepts

OOP Pillars

SOLID Principles

Creational Patterns

Structural Patterns

Behavioral Patterns

Tier 2

Tier 3

Tier 4

Tier 5

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.


Quick check

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?

Choose one answer

2. How It Works

Participants:

  • Subject (also called Observable) — maintains a list of observers; provides attach(), detach(), and notify() 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:

  1. Define the Observer with update()
  2. Define the Subject with attach, detach, notify
  3. ConcreteSubject holds state; on each state change, calls notify() which loops over registered observers and calls their update()
  4. ConcreteObserver registers itself with the subject; on update(), 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 calls subject.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
    }
}

One price change reaches each observer

StockTicker pushes each price to its registered observers; the dashboard always updates, while the alert reacts only above its threshold.

The ticker stores a price of 175 before notifying its registered observers.

Text version
  1. The ticker stores a price of 175 before notifying its registered observers.
  2. The dashboard receives the price directly in the event and updates its display.
  3. The alert observer receives the event but stays silent because 175 is below 180.
  4. The ticker stores the new price of 185 before notifying its observers again.
  5. The dashboard receives the new event and displays 185.
  6. The alert observer prints an alert because 185 exceeds its threshold of 180.
Quick check

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?

Choose one answer

3. Variants & Comparisons

VariantHow It WorksProsConsBest For
Push modelSubject sends event data in update(event)Observer needs no back-reference to subjectObservers receive data they may not needEvent-driven systems, decoupled observers
Pull modelSubject calls update() with no data; observer queries subjectObserver only fetches what it needsObserver must hold subject referenceObservers needing selective state
Event Bus / MediatorEvents published to a bus; observers subscribe by typeSubjects and observers never reference each otherExtra indirection, harder to traceLarge 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: Simple lists (as above) are common. The built-in signal module handles OS signals, not application events. __slots__ + property setters can trigger observers cleanly.


Choose how observers receive state

Push sends event data directly, pull lets observers query state, and an event bus routes events by type with extra indirection.

Push sends state, pull queries it, and an event bus routes it by type.

Text version
  1. Push sends state, pull queries it, and an event bus routes it by type.
  2. Pull fetches only needed state, while push and event buses spare observers a subject reference.
  3. The costs are unneeded data with push, a subject reference with pull, and harder tracing through a bus.

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 detach observers. 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.

Quick check

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?

Choose one answer

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.


Quick check

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?

Choose one answer

6. Interview Cheat Sheet

Key sentences:

  1. " decouples the thing that changes (Subject) from the things that react (Observers) — the subject only knows about the , not concrete implementations."
  2. "Push model sends data to observers; pull model lets observers query. Push is more decoupled; pull avoids sending irrelevant data."
  3. "The classic pitfall is memory leaks — if you don't detach observers, the subject holds references forever, preventing garbage collection."
  4. "In modern Java, PropertyChangeListener and 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)

Reading

On this page

My notes

Sign in to keep highlights and notes.

Glossary history

Click dotted jargon to save explanations here.