Observer
Defines a one-to-many dependency between objects: when the subject changes state, all its observers are notified and updated automatically — without the subject needing to know each of them.
Intent
Establish a one-to-many dependency relationship so
that, when the subject (Subject / Observable) changes its
internal state, all registered observers are notified
automatically. The subject doesn't know the concrete types of the
observers — it communicates only through the Observer
interface.
Cataloged by the GoF (1994) as a behavioral pattern, Observer is the conceptual foundation of event systems, data-binding in UI frameworks (React, Vue, Angular), reactive streams (RxJS, ReactiveX), and any notification mechanism where the data producer must not be tightly coupled to its consumers.
Problem
Imagine a weather station that measures temperature, humidity, and pressure. Multiple displays need to show this data: a current-readings panel, a historical chart, and an alerts display. The naive approach would couple the station directly to each display:
// Naive approach — DON'T do this:
class WeatherStation {
private currentPanel: CurrentPanel;
private historyChart: HistoryChart;
private alertsDisplay: AlertsDisplay;
update(temp: number, humidity: number, pressure: number): void {
this.currentPanel.show(temp, humidity, pressure);
this.historyChart.record(temp, humidity, pressure);
this.alertsDisplay.check(temp, humidity, pressure);
// Every new display requires modifying this class.
}
}
Adding or removing a display requires modifying
WeatherStation — a violation of the Open/Closed Principle.
Displays also can't be registered or removed at runtime. Observer
solves both problems: the station doesn't know the displays, it just
publishes changes; displays subscribe and unsubscribe independently.
Solution
Observer organizes the code into three participants:
-
Subject (Observable): keeps a list of observers and
offers methods to register them (
subscribe) and remove them (unsubscribe). When its state changes, it callsnotifyto walk the list and inform each observer. -
Observer (interface): declares the update method
that the Subject calls. E.g.:
update(data). -
ConcreteObserver: implements
Observerand reacts to the notification according to its responsibility — displaying data, logging, sending an alert.
Push vs Pull
There are two approaches to what the Subject sends in the notification:
-
Push: the Subject sends the current data directly
in the notification argument —
update(temp, humidity, pressure). It's more direct, but it couples theObserver's signature to the exact shape of the Subject's data. When the state grows, the Observer interface changes. -
Pull: the Subject notifies without data (or with a
reference to itself) —
update(subject). Each Observer asks the Subject only for what it needs. More flexible: new data can be added to the Subject without breaking the Observer's signature. The cost is that the Observer needs to know the Subject's interface to pull data.
Observer vs Pub/Sub
Observer and Publish/Subscribe are frequently confused, but they differ in coupling:
- Observer: the Subject directly knows its Observers (it keeps the list). There's direct coupling Subject ↔ Observer, but not between Observers.
- Pub/Sub: introduces a broker (event bus) between publishers and subscribers. Publishers and subscribers don't know each other — they communicate only through the channel/topic. Decoupling is total, but flow traceability is lower.
Use Observer when the number of observers is manageable and the Subject–Observer relationship is direct. Use Pub/Sub when communication needs to cross modules or services without direct coupling.
Structure
«interface»
Observer
┌──────────────────────────┐
│ + update(data: T): void │
└──────────────────────────┘
▲
┌──────────┴─────────────────────┐
│ │
CurrentPanel HistoryChart
(ConcreteObserver) (ConcreteObserver)
Subject
┌───────────────────────────────────────────┐
│ - observers: Observer[] │
│ + subscribe(o: Observer): void │
│ + unsubscribe(o: Observer): void │
│ # notify(): void │
└───────────────────────────────────────────┘
▲
WeatherStation (ConcreteSubject)
┌───────────────────────────────────────────┐
│ - temperature: number │
│ - humidity: number │
│ + setReading(t, h): void │
│ → calls this.notify() │
└───────────────────────────────────────────┘
Notification flow (push model):
station.setReading(25, 60)
→ notify()
→ panel.update({ temperature: 25, humidity: 60 })
→ chart.update({ temperature: 25, humidity: 60 })
→ alerts.update({ temperature: 25, humidity: 60 })
Code examples
Example 1 — Weather station with multiple displays
A complete implementation with a generic Subject, subscribe/unsubscribe and notification. Uses the push model with a typed data object.
// ── Interfaces ────────────────────────────────────────────────
interface WeatherData {
temperature: number;
humidity: number;
}
interface Observer<T> {
update(data: T): void;
}
// ── Generic Subject ──────────────────────────────────────────
class Subject<T> {
private readonly observers: Set<Observer<T>> = new Set();
subscribe(observer: Observer<T>): void {
this.observers.add(observer);
}
unsubscribe(observer: Observer<T>): void {
this.observers.delete(observer);
}
protected notify(data: T): void {
for (const observer of this.observers) {
observer.update(data);
}
}
}
// ── ConcreteSubject ───────────────────────────────────────────
class WeatherStation extends Subject<WeatherData> {
private temperature = 0;
private humidity = 0;
setReading(temperature: number, humidity: number): void {
this.temperature = temperature;
this.humidity = humidity;
this.notify({ temperature: this.temperature, humidity: this.humidity });
}
}
// ── ConcreteObservers ─────────────────────────────────────────
class CurrentPanel implements Observer<WeatherData> {
update(data: WeatherData): void {
console.log(
`[Panel] Temp: ${data.temperature}C Humidity: ${data.humidity}%`
);
}
}
class AlertsDisplay implements Observer<WeatherData> {
update(data: WeatherData): void {
if (data.temperature > 35) {
console.log("[ALERT] Critical temperature:", data.temperature + "C");
}
if (data.humidity < 30) {
console.log("[ALERT] Low humidity:", data.humidity + "%");
}
}
}
class HistoryChart implements Observer<WeatherData> {
private readonly history: WeatherData[] = [];
update(data: WeatherData): void {
this.history.push(data);
console.log(`[Chart] Records: ${this.history.length}`);
}
getData(): readonly WeatherData[] {
return this.history;
}
}
// ── Usage ────────────────────────────────────────────────────
const station = new WeatherStation();
const panel = new CurrentPanel();
const alerts = new AlertsDisplay();
const chart = new HistoryChart();
station.subscribe(panel);
station.subscribe(alerts);
station.subscribe(chart);
station.setReading(25, 60);
// [Panel] Temp: 25C Humidity: 60%
// [Chart] Records: 1
station.setReading(38, 25);
// [Panel] Temp: 38C Humidity: 25%
// [ALERT] Critical temperature: 38C
// [ALERT] Low humidity: 25%
// [Chart] Records: 2
// Unsubscribe the panel — it stops receiving notifications.
station.unsubscribe(panel);
station.setReading(20, 70);
// [Chart] Records: 3 (panel was not notified)
<?php
// ── Interfaces ────────────────────────────────────────────────
// PHP has native SplSubject and SplObserver; here we use manual
// interfaces for clarity and explicit typing.
interface WeatherData
{
public function getTemperature(): float;
public function getHumidity(): float;
}
interface WeatherObserver
{
public function update(WeatherData $data): void;
}
// ── Value Object ──────────────────────────────────────────────
final class Reading implements WeatherData
{
public function __construct(
private readonly float $temperature,
private readonly float $humidity
) {}
public function getTemperature(): float { return $this->temperature; }
public function getHumidity(): float { return $this->humidity; }
}
// ── ConcreteSubject ───────────────────────────────────────────
class WeatherStation
{
/** @var WeatherObserver[] */
private array $observers = [];
public function subscribe(WeatherObserver $observer): void
{
$this->observers[] = $observer;
}
public function unsubscribe(WeatherObserver $observer): void
{
$this->observers = array_filter(
$this->observers,
fn($o) => $o !== $observer
);
}
public function setReading(float $temperature, float $humidity): void
{
$data = new Reading($temperature, $humidity);
foreach ($this->observers as $observer) {
$observer->update($data);
}
}
}
// ── ConcreteObservers ─────────────────────────────────────────
class CurrentPanel implements WeatherObserver
{
public function update(WeatherData $data): void
{
printf(
"[Panel] Temp: %.1fC Humidity: %.1f%%\n",
$data->getTemperature(),
$data->getHumidity()
);
}
}
class AlertsDisplay implements WeatherObserver
{
public function update(WeatherData $data): void
{
if ($data->getTemperature() > 35) {
echo "[ALERT] Critical temperature: " . $data->getTemperature() . "C\n";
}
if ($data->getHumidity() < 30) {
echo "[ALERT] Low humidity: " . $data->getHumidity() . "%\n";
}
}
}
class HistoryChart implements WeatherObserver
{
/** @var WeatherData[] */
private array $history = [];
public function update(WeatherData $data): void
{
$this->history[] = $data;
echo "[Chart] Records: " . count($this->history) . "\n";
}
}
// ── Usage ────────────────────────────────────────────────────
$station = new WeatherStation();
$panel = new CurrentPanel();
$alerts = new AlertsDisplay();
$chart = new HistoryChart();
$station->subscribe($panel);
$station->subscribe($alerts);
$station->subscribe($chart);
$station->setReading(25, 60);
// [Panel] Temp: 25.0C Humidity: 60.0%
// [Chart] Records: 1
$station->setReading(38, 25);
// [Panel] Temp: 38.0C Humidity: 25.0%
// [ALERT] Critical temperature: 38C
// [ALERT] Low humidity: 25%
// [Chart] Records: 2
// Unsubscribe the panel.
$station->unsubscribe($panel);
$station->setReading(20, 70);
// [Chart] Records: 3
Example 2 — Typed event system (pull model)
In the pull model, the Subject only notifies that something changed (passing a reference to itself). Each Observer queries the data it needs. This pattern is more flexible when the Subject has rich state and different Observers need distinct subsets of information.
// Pull model: the Observer receives a reference to the Subject
// and "pulls" only the data it needs.
interface OrderObserver {
onOrderUpdated(order: Order): void;
}
class Order {
private readonly observers: Set<OrderObserver> = new Set();
private _status: string = "draft";
private _items: string[] = [];
subscribe(o: OrderObserver): void { this.observers.add(o); }
unsubscribe(o: OrderObserver): void { this.observers.delete(o); }
get status(): string { return this._status; }
get items(): readonly string[] { return this._items; }
get total(): number { return this._items.length * 50; } // $50/item (simplified)
addItem(item: string): void {
this._items.push(item);
this.notify();
}
advanceStatus(newStatus: string): void {
this._status = newStatus;
this.notify();
}
private notify(): void {
for (const o of this.observers) {
o.onOrderUpdated(this); // passes `this` — the Observer pulls what it needs
}
}
}
// Observer that only cares about the status:
class StatusLogger implements OrderObserver {
onOrderUpdated(order: Order): void {
console.log(`[Log] Current status: ${order.status}`);
}
}
// Observer that only cares about the total:
class TotalCalculator implements OrderObserver {
onOrderUpdated(order: Order): void {
console.log(`[Total] $${order.total.toFixed(2)} (${order.items.length} items)`);
}
}
// ── Usage ────────────────────────────────────────────────────
const order = new Order();
order.subscribe(new StatusLogger());
order.subscribe(new TotalCalculator());
order.addItem("Keyboard");
// [Log] Current status: draft
// [Total] $50.00 (1 items)
order.addItem("Mouse");
// [Log] Current status: draft
// [Total] $100.00 (2 items)
order.advanceStatus("confirmed");
// [Log] Current status: confirmed
// [Total] $100.00 (2 items)
<?php
// Pull model in PHP — Observer receives a reference to the Subject.
interface OrderObserver
{
public function onOrderUpdated(Order $order): void;
}
class Order
{
/** @var OrderObserver[] */
private array $observers = [];
private string $status = 'draft';
/** @var string[] */
private array $items = [];
public function subscribe(OrderObserver $o): void
{
$this->observers[] = $o;
}
public function unsubscribe(OrderObserver $o): void
{
$this->observers = array_filter($this->observers, fn($obs) => $obs !== $o);
}
public function getStatus(): string { return $this->status; }
public function getItems(): array { return $this->items; }
public function getTotal(): float { return count($this->items) * 50.0; }
public function addItem(string $item): void
{
$this->items[] = $item;
$this->notify();
}
public function advanceStatus(string $newStatus): void
{
$this->status = $newStatus;
$this->notify();
}
private function notify(): void
{
foreach ($this->observers as $o) {
$o->onOrderUpdated($this);
}
}
}
class StatusLogger implements OrderObserver
{
public function onOrderUpdated(Order $order): void
{
echo "[Log] Current status: " . $order->getStatus() . "\n";
}
}
class TotalCalculator implements OrderObserver
{
public function onOrderUpdated(Order $order): void
{
printf(
"[Total] $%.2f (%d items)\n",
$order->getTotal(),
count($order->getItems())
);
}
}
// ── Usage ────────────────────────────────────────────────────
$order = new Order();
$order->subscribe(new StatusLogger());
$order->subscribe(new TotalCalculator());
$order->addItem('Keyboard');
// [Log] Current status: draft
// [Total] $50.00 (1 items)
$order->addItem('Mouse');
// [Log] Current status: draft
// [Total] $100.00 (2 items)
$order->advanceStatus('confirmed');
// [Log] Current status: confirmed
// [Total] $100.00 (2 items)
When to use
- When a change in one object requires updating others and you don't know how many or which ones at design time. Notification systems, live data feeds, processing pipelines.
- When observers need to be registered and removed dynamically at runtime — modules that enter and leave the system without requiring a recompile.
- To implement MVC/MVP: the Model is the Subject; Views are Observers that update automatically when the Model changes. It's the foundation of data-binding in Angular, Vue and similar frameworks.
- When you want to decouple the data producer from its consumers without introducing an intermediate broker (use Pub/Sub for that).
When to avoid
- When notification order matters and is critical: Observer doesn't guarantee notification order by default. If Observer B depends on Observer A having already processed the notification, the pure pattern doesn't guarantee that — extra logic is needed.
- When observers are few, fixed, and known: a direct call is simpler and more traceable than setting up the Observer infrastructure.
-
When cascading notifications are likely: if an
Observer modifies the Subject during
update, triggering new notifications, the system can enter loops or exhibit unpredictable behavior.
Pros and cons
Pros
- Decouples the Subject from its Observers — the Subject doesn't need to know their concrete types.
- Observers can be added and removed at runtime without changing the Subject.
- Follows the Open/Closed Principle — new Observers don't require modifying the Subject.
- Natural foundation for reactive systems, data-binding, and event-driven architecture.
Cons
- Memory leaks: Observers that aren't removed keep the Subject (and themselves) alive — a classic pitfall in garbage-collected languages.
- Indeterminate notification order: dependencies between Observers are hard to manage.
- Cascading updates: an Observer that modifies the Subject can trigger unexpected notifications.
- Hard to trace the data flow: with many Observers, understanding "who triggered what" requires specific debugging tools.
Common pitfalls
1. Memory leaks from not unsubscribing
The most frequent pitfall: registering an Observer and forgetting to
call unsubscribe when it's no longer needed. As long as
the Subject exists, it keeps a strong reference to the Observer —
preventing the GC from collecting the object. In long-lived
environments (servers, SPAs), the accumulation of "dead" Observers
causes memory leaks.
Solution: use a Set (makes removal by
reference easier), return a cancellation function (disposable)
from subscribe, or use WeakRef for optional
Observers. In TypeScript with RxJS, use takeUntil and
unsubscribe in the component's lifecycle.
2. Cascading updates
If an Observer calls a Subject method that triggers another round of
notifications, the result can be an infinite loop or an unpredictable
update order. Rule of thumb: Observers shouldn't modify the Subject's
state during update. If needed, use an event-queue
mechanism (next tick, microtask, setTimeout) to process the change
outside the current notification cycle.
3. Excessive notifications
A Subject that notifies on every small change (e.g.: every keystroke in a text field) can overload slow Observers. Mitigation techniques: debounce (groups changes within a time window), dirty flag (notifies only if the value actually changed), or batch notification (accumulates changes and notifies once at the end of the cycle).
4. Confusing Observer with Pub/Sub
Observer implies direct knowledge: the Subject keeps the list of Observers and calls them directly. Pub/Sub uses an intermediate broker (EventEmitter, Message Bus) that fully decouples publishers from subscribers. Use Observer when direct coupling is acceptable; use Pub/Sub when you need decoupling between independent modules or services.
Related patterns
Observer relates to other behavioral and structural patterns:
Strategy and Observer both use interfaces for decoupling, but for different purposes: Strategy swaps algorithms in the context; Observer distributes state notifications to multiple receivers. State also manages implicit notifications when the object changes state — in some implementations, the State notifies an Observer or uses a Subject to propagate the change. Mediator centralizes communication between objects, eliminating direct references between them — it's an alternative to Observer when many objects need to communicate bidirectionally, since Observer still couples the Subject to its Observers through the registration list.