Behavioral Pattern Pattern 19 of 23

Observer

Think of a YouTube channel. When the creator uploads a new video, every single subscriber gets notified — the creator never personally calls each subscriber's phone number. The channel just announces "new video," and whoever is subscribed reacts. The Observer pattern is that exact relationship in code: one object (the "subject") notifies a list of interested objects (the "observers") whenever something changes, without needing to know who those observers are or what they'll do about it.

⚠️ The Problem

Imagine a WeatherStation class that reads a new temperature every few minutes. You need a phone app display, a desktop widget, and a logging service to all react every time the temperature updates. The naive approach: hardcode calls to each one directly inside WeatherStationphoneApp.update(temp); desktopWidget.update(temp); logger.log(temp);. Now adding a fourth display means editing WeatherStation itself, and WeatherStation has to import and know about every consumer that will ever exist. It's tightly coupled to code that has nothing to do with reading a thermometer.

✅ The Solution

Give the subject a generic list of observers and two methods: subscribe(observer) and notify(). Anyone who wants updates calls subscribe and hands over an object with a known method (like update()). The subject's notify() just loops over that list and calls update() on each one — it has no idea whether it's talking to a phone app, a widget, or a logger, and it never needs to. New observers can be added or removed at runtime without ever touching the subject's code.

Example 1 — a weather station, the classic case

Primary Example — JavaScript
class WeatherStation {
  #observers = [];

  subscribe(observer) {
    this.#observers.push(observer);
  }

  unsubscribe(observer) {
    this.#observers = this.#observers.filter((o) => o !== observer);
  }

  setTemperature(temp) {
    this.temperature = temp;
    this.#notify();
  }

  #notify() {
    for (const observer of this.#observers) {
      observer.update(this.temperature);
    }
  }
}

const phoneApp = { update: (t) => console.log(`Phone app: ${t}°C`) };
const logger   = { update: (t) => console.log(`[LOG] temp changed to ${t}°C`) };

const station = new WeatherStation();
station.subscribe(phoneApp);
station.subscribe(logger);

station.setTemperature(24); // both phoneApp and logger react automatically

WeatherStation never imports phoneApp or logger by name — it just calls .update() on whatever's in its list. Adding a third observer (say, a desktop widget) means calling station.subscribe(desktopWidget) from outside; nothing inside WeatherStation changes at all.

Example 2 — a secondary context: a shopping cart's total

Secondary Example — Python
class Cart:
    def __init__(self):
        self._items = []
        self._observers = []

    def subscribe(self, observer):
        self._observers.append(observer)

    def add_item(self, price):
        self._items.append(price)
        self._notify()

    def _notify(self):
        total = sum(self._items)
        for observer in self._observers:
            observer(total)

def update_total_display(total):
    print(f"Cart total: ${total}")

def update_free_shipping_banner(total):
    print("Free shipping unlocked!" if total > 50 else "Add more for free shipping")

cart = Cart()
cart.subscribe(update_total_display)
cart.subscribe(update_free_shipping_banner)

cart.add_item(30)
cart.add_item(25)  # both functions fire automatically as the total changes

Same relationship, a completely different domain, and observers here are plain functions instead of objects with an update method — Observer doesn't require any particular shape for the observer, only that the subject has a consistent way to reach it.

👍 When To Use It

  • One change needs to trigger reactions in an unknown or changing number of other parts of the system — UI updates, event systems, and pub/sub messaging are the classic cases.
  • You want to decouple the thing that changes from the things that react to it, so new reactions can be added without modifying the source of the change.
  • You're building something event-driven — this pattern is the conceptual foundation of DOM events, React state updates, and most "on change" callback systems.

👎 When NOT To Use It

  • When there's only ever one fixed listener — a direct method call is simpler and easier to trace than setting up a subscription for something that will never have a second subscriber.
  • When notification order or reliable delivery matters a lot — plain Observer typically notifies synchronously in subscription order with no guarantees around failure handling; a dedicated message queue is a better fit if an observer failing must not silently break the others.
  • When it would create long, hard-to-trace chains — if notifying one observer triggers it to update something that notifies yet another observer, debugging "what caused this update" can become genuinely difficult.

❓ FAQ

Is Observer the same thing as pub/sub?

They're closely related but not identical. Classic Observer has subjects and observers talking directly to each other (the subject holds a list of observers and calls them directly). Pub/sub usually adds a middleman — a message broker or event bus — so publishers and subscribers don't hold references to each other at all, which scales better across separate services but adds infrastructure.

What happens if an observer throws an error during notify()?

In a naive implementation, exactly what you'd expect from any loop: the error propagates and any observers later in the list never get notified. Production implementations usually wrap each observer call in its own try/catch so one broken observer can't block the rest — this isn't part of the "classic" pattern definition, but it's an important practical addition.

How is this different from the Mediator pattern?

Observer is a one-to-many broadcast: one subject, many observers, one direction of communication (subject → observers). Mediator centralizes many-to-many communication between a set of objects that would otherwise need direct references to each other — it's less about "announcing a change" and more about "coordinating a conversation." See the Mediator lesson later in this track for the direct comparison.

🙋 There Are No Dumb Questions

Q: Isn't this just event listeners, like addEventListener in the browser?
A: Yes, essentially — addEventListener/removeEventListener/dispatching an event is a real-world, built-in implementation of the Observer pattern. If you've ever called button.addEventListener("click", handler), you've already used this pattern; this lesson is really just naming and generalizing something you likely already do daily.

✅ Quick Check

In the Observer pattern, what does the subject actually need to know about its observers?


Next up: what happens when an object needs to behave completely differently depending on its current state, without a wall of if/else checks scattered everywhere? That's the State pattern.

← Back to
Next →