Understanding the Observer Pattern
An explorable introduction to the Observer pattern: how subjects notify observers, and how it compares to pub-sub.
The Observer pattern is a behavioral design pattern where an object (the subject) maintains a list of dependents (observers) and notifies them automatically when its state changes. It's one of the most common patterns in UI and event-driven systems.
The Observer pattern is one of my favorite design patterns—not only because it was the very first design pattern I ever learnt, but because once you know its basic principle, you start to see it everywhere.
From real-time updates with WebSockets, to DOM events in the browser, and even in large-scale backend system design, it's literally everywhere. If we consider pub-sub as an extension of the pattern, its usage is even wider.
In this post I'll show you how it works and the different variations that make it so useful. By the end you'll have a solid understanding and will start to appreciate its many applications in your own projects.
Let's start from an example
Imagine we're on a farm with crops in different areas. We care about soil moisture, water usage, fertiliser saturation, and so on.
On a small farm we could hire a few people to collect soil samples each day and analyse them. Once we know the saturation levels we can act: if the chili field is too dry, we water it; if the eggplant field's soil is unhealthy, we fertilise it.
On a bigger farm that approach doesn't scale—there's too much to check. We need a smarter way. Instead of us periodically checking conditions, what if the fields could tell us when something changes? Most of the time things are fine; we only care about abnormal events, like drought or temperature spikes.
Suppose we deploy sensors in the soil that report their status. We then simply listen to those events and respond: water the field, or turn on the heater if it's too cold.
That's the Observer pattern in practice: we don't actively poll the subject's status; we passively listen for changes.
How it works
There are two roles: subject and observer. A subject can have many observers. In our farm example, a water sensor in the soil is the subject. A mobile app might listen to it (one observer), and an irrigation system might also listen (another observer).
1) Subject only: state changes exist
First, the sensor works on its own. Its state can change (e.g. moisture: Dry → OK → Wet) even with no observers. Click the water sensor to cycle through moisture states.
Typically an observer registers with a subject so it can be notified when things change. The change might come from elsewhere (e.g. weather) or from the subject itself (e.g. the sensor reading moisture and notifying its observers).
2) Add Observer A: the mobile app
We add a Mobile observer. When the sensor’s state changes, the mobile app is notified and can show the current moisture (e.g. "Dry", "OK", "Wet").
3) Add Observer B: the irrigation system
We add a second observer—Irrigation. Now both Mobile and Irrigation receive the same updates. Each observer can react in its own way (e.g. show a reading vs. turn the water on).
4) Unsubscribe: stop receiving notifications
An observer can detach when it’s no longer interested. In the diagram below, drag an edge endpoint away from a node to unsubscribe; that observer will no longer receive updates.
The subject holds references to its observers. When something meaningful happens, it notifies each one. There is a direct relationship: the subject knows who is observing it.
Interesting, can you show me some code?
The diagram gives you the big picture—but how does this look in code? Let's build it step by step in TypeScript. The same ideas apply in other object-oriented languages.
Step 1: A simple subject
We start with a WaterSensor—a thing that can be observed. It holds a status that can change over time:
type Status = 'Dry' | 'OK' | 'Wet' | 'NA';
class WaterSensor {
private _status: Status = 'NA';
set status(value: Status) {
this._status = value;
}
get status(): Status {
return this._status;
}
}So far it's just a plain object. When we update the status (e.g. when the soil moisture changes), nothing else happens—we have no way to notify anyone.
Step 2: Define the observer interface
We need something that can receive notifications. Let's define an interface:
interface Observer {
notify(status: Status): void;
}Any class that implements notify(status) can act as an observer. The subject doesn't care what it does—it just calls notify when something changes.
Step 3: Add a list of observers to the subject
The subject needs to keep track of who's listening. We add an array and a subscribe method:
class WaterSensor {
private _status: Status = 'NA';
private _observers: Observer[] = [];
subscribe(observer: Observer): void {
this._observers.push(observer);
}
set status(value: Status) {
this._status = value;
this._observers.forEach((o) => o.notify(value));
}
get status(): Status {
return this._status;
}
}Whenever the status changes, we loop through all observers and call notify with the new value. That's the core of the pattern.
Step 4: Use it
At the call site, we create a sensor, attach observers, and update the status. Everyone subscribed gets notified automatically:
const sensor = new WaterSensor();
const mobileApp: Observer = {
notify(status) {
console.log(`Mobile: moisture is ${status}`);
},
};
const irrigation: Observer = {
notify(status) {
if (status === 'Dry') console.log('Irrigation: turning water on');
},
};
sensor.subscribe(mobileApp);
sensor.subscribe(irrigation);
sensor.status = 'Dry'; // Both observers are notifiedNo polling, no manual checks—the subject pushes updates to whoever is listening. That's the Observer pattern in code.
Try it yourself: add observers, change the sensor status, and watch the output. You can also show the generated TypeScript to see how your setup maps to real code.
Why it matters
- Loose coupling: Observers depend on the subject’s interface, not its concrete implementation.
- Dynamic relationships: You can add and remove observers at runtime.
- Broadcast updates: One change can propagate to many observers without the subject knowing their details.
I'm sharing this first to get your feedback. In a follow-up, I'll expand on how Observer compares to pub-sub and when to choose one over the other. To be continued…