The observer pattern is a behavioral design pattern in which an object called the subject keeps a list of dependent objects called observers and notifies them automatically whenever its state changes. In software architecture and programming it establishes a one-to-many dependency without tight coupling: the subject knows only that its observers implement a common notification interface, not what they do with the update.
The pattern was cataloged by the “Gang of Four” in 1994, but its idea is older and now appears everywhere: GUI event listeners, data binding in MVC and MVVM frameworks, reactive streams, spreadsheet recalculation and domain events. Understanding it explains both the flexibility of event-driven code and its typical problems, such as memory leaks, unpredictable ordering and update cascades.
How the observer pattern works
The classic structure has four roles. The subject interface offers methods to attach and detach observers. A concrete subject holds the state of interest and calls notify() when it changes. The observer interface declares an update() method, and each concrete observer implements it to react, for example by redrawing a chart or writing a log entry.
| Participant | Responsibility | Example |
|---|---|---|
| Subject | Defines attach, detach and notify operations | An Observable base class |
| Concrete subject | Stores state and triggers notifications | A stock price feed |
| Observer | Declares the update interface | A PriceListener interface |
| Concrete observer | Reacts to the change | A chart widget or an alert service |
The sequence is simple: observers register with the subject, the subject’s state changes, the subject iterates over its list and calls each observer, and observers may later unregister. Because the subject depends only on the interface, new observers can be added without modifying the subject, which follows the open-closed principle from the SOLID principles.
Push and pull notification models
Implementations differ in how much data travels with the notification. In the push model, the subject sends the changed data with the update call. In the pull model, the subject only signals that something changed, and each observer queries the subject for the details it needs.
| Aspect | Push model | Pull model |
|---|---|---|
| Data in the notification | Changed values or an event object | Only a reference to the subject |
| Coupling | Observers depend on the event format | Observers depend on the subject’s query methods |
| Efficiency | Good when all observers need the same data | Good when observers need different data |
| Consistency risk | Low, the snapshot is sent together | State may change again before the pull |
| Typical use | Event objects in UI frameworks | Model-view refresh in simple MVC |
Modern code often uses a hybrid: a small event object describes what changed, and observers can fetch more detail if needed. Event objects also make it easy to filter notifications by event type.
Observer pattern compared with related approaches
Several neighboring concepts share the observer idea but change who knows whom and when delivery happens. The distinction matters when choosing an architecture.
| Mechanism | Coupling | Delivery | Typical scope |
|---|---|---|---|
| Observer pattern | Observers register directly with the subject | Usually synchronous, in process | Objects inside one application |
| Publish-subscribe | Publishers and subscribers know only a broker or topic | Often asynchronous | Modules or distributed services |
| Event bus | Components share a central dispatcher | Synchronous or queued | Large applications with many modules |
| Reactive streams | Observers subscribe to composable streams | Asynchronous with backpressure | Data pipelines and UI state |
| Callback | One caller passes one function | Single notification target | Simple asynchronous APIs |
In MVC and MVVM, the observer pattern is the mechanism that keeps views synchronized with models: the view observes the model, so changes in data appear on screen without the model knowing anything about rendering. This supports separation of concerns.
Implementing the observer pattern well
Most languages and frameworks provide built-in support, such as events and delegates in C#, listener interfaces in Java, EventTarget in browser JavaScript and signals in many UI toolkits. Writing a custom subject is still common in domain code, and a few decisions determine whether it remains reliable.
- Copy the observer list before notifying, so observers can detach themselves during an update without breaking the iteration.
- Define ordering explicitly if it matters; otherwise document that observers must not depend on order.
- Isolate failures: one throwing observer should not prevent others from receiving the notification.
- Return a subscription handle from attach so callers can reliably unsubscribe later.
- Decide on threading: notify on the caller’s thread, a UI thread or a queue, and document the choice.
For high-frequency changes, batching or debouncing notifications avoids redundant work. A form that updates on every keystroke can coalesce changes so that expensive observers, such as validation or network calls, run only after input pauses.
Pitfalls of the observer pattern
The flexibility of implicit notifications comes at a cost: control flow becomes harder to follow, because a single state change can trigger code in many places. The most common problems and their remedies are listed below.
| Problem | Cause | Remedy |
|---|---|---|
| Lapsed listener memory leak | Observer never unsubscribes, subject keeps it alive | Unsubscribe on disposal or use weak references |
| Update cascades | Observers change other subjects in turn | Batch updates, avoid cyclic dependencies |
| Infinite loops | Two objects observe and update each other | Check for real changes before notifying |
| Order dependence | Observers assume a fixed call order | Remove the assumption or use explicit stages |
| Thread-safety bugs | Notifications arrive on unexpected threads | Marshal to a defined thread or lock the list |
The lapsed listener problem is especially frequent in long-running GUI applications: a closed window remains in memory because a global service still holds a reference to it as an observer. Tying subscriptions to the lifecycle of the observer solves most of these leaks.
A non-technical view
The observer pattern works like a newsletter subscription. You sign up once. Whenever there is news, it arrives automatically. You never have to keep checking the website.
In a program, one part holds some information, and other parts subscribe to it. When the information changes, every subscriber is told at once. The sender does not need to know what each subscriber does with the news, which keeps the program flexible and easy to extend.
A concrete example
A logistics company builds a dashboard that tracks delivery vehicles. A vehicle position service acts as the subject and receives GPS updates every few seconds. Three observers subscribe to it: a map widget that moves the vehicle icon, an arrival estimator that recalculates delivery times and an alert module that warns dispatchers when a vehicle leaves its planned route.
When the team later adds a fourth observer that records fuel efficiency, they do not touch the position service at all; they simply register the new component. During testing they discover that closed map tabs were never unsubscribing, so memory grew all day. Binding each subscription to the tab’s lifecycle fixed the leak, and the dashboard now runs for weeks with stable memory use.