Observer Pattern
Define a subscription mechanism to notify multiple objects about events. A behavioral design pattern for event-driven communication.
Overview
The Observer Pattern is a behavioral design pattern that defines a subscription mechanism to notify multiple objects about events happening to the object they are observing. It establishes a one-to-many dependency between objects.
It is the foundation of event-driven architectures, reactive programming, and the Model-View architecture in UI frameworks.
When to Use
Use the Observer Pattern when:
- Changes to one object require updating an unknown number of dependent objects. See Mediator Pattern for centralized routing.
- You need a publish-subscribe communication model. See CQRS Pattern for event-driven architectures.
- An object should notify others without knowing who they are
- You want loose coupling between event producers and consumers
- Building reactive UI components or real-time data feeds. See API REST for real-time data fetching.
Solution
Python
class Subject:
def __init__(self):
self._observers = []
def attach(self, observer):
self._observers.append(observer)
def notify(self, data):
for observer in self._observers:
observer.update(data)
class Observer:
def update(self, data):
print(f"Received: {data}")
# Usage
subject = Subject()
subject.attach(Observer())
subject.attach(Observer())
subject.notify("Hello observers!")
JavaScript
class Subject {
constructor() {
this.observers = [];
}
subscribe(fn) {
this.observers.push(fn);
}
notify(data) {
this.observers.forEach((fn) => fn(data));
}
}
// Usage
const subject = new Subject();
subject.subscribe((data) => console.log("A:", data));
subject.subscribe((data) => console.log("B:", data));
subject.notify("Hello observers!");
Java
import java.util.ArrayList;
import java.util.List;
interface Observer {
void update(String data);
}
class Subject {
private final List<Observer> observers = new ArrayList<>();
void attach(Observer o) {
observers.add(o);
}
void notifyObservers(String data) {
for (Observer o : observers) {
o.update(data);
}
}
}
// Usage
Subject subject = new Subject();
subject.attach(data -> System.out.println("Received: " + data));
subject.notifyObservers("Hello observers!");
Explanation
The Observer Pattern consists of two core roles:
- Subject (Publisher): Maintains a list of observers and sends notifications
- Observer (Subscriber): Defines an interface for objects that should be notified of changes
When the Subject’s state changes, it iterates over its observers and calls their update method. Observers can subscribe or unsubscribe dynamically without the Subject knowing concrete classes.
Variants
| Variant | Use Case | Trade-off |
|---|---|---|
| Push model | Subject sends full data to observers | Simple, but may send unnecessary data |
| Pull model | Subject notifies; observers query for details | More efficient, but adds round-trips |
| Event bus | Central dispatcher decouples subjects and observers | More flexible, adds indirection |
What Works
- Unsubscribe observers when they are destroyed to prevent memory leaks
- Avoid circular updates where observers trigger changes back to the subject
- Use weak references in languages that support them (e.g., Java) for automatic cleanup
- Keep notification logic simple and avoid heavy computations in the notify loop
- Document event payloads so observers know what data to expect
Common Mistakes
- Memory leaks: Forgetting to detach observers when they are no longer needed
- Unexpected update order: Observers may run in an undefined order; do not rely on it
- Infinite loops: An observer that modifies the subject can trigger cascading updates
- Tight coupling: Giving observers access to the full subject instead of just the data they need
- Synchronous blocking: Running slow observers in the main notification thread
Troubleshooting
- Pattern does not fit the problem: re-evaluate the forces (performance, scalability, team size, coupling). A pattern is only appropriate when its trade-offs match your constraints.
- Too many abstractions: if adding a pattern increases complexity without a clear benefit, simplify. Not every module needs a factory, decorator, or strategy.
- Tight coupling after refactoring: check that interfaces are stable and dependencies point inward.
- Tests break when the design changes: favor stable contracts over internal structure.
- Performance regression from indirection: measure before and after. Layers, decorators, and adapters can add latency; cache or inline hot paths if needed.
Production Notes
- Deploy gradually using canary or blue-green to catch regressions early.
- Configure alerts for error rate, p99 latency, and failure rate before enabling in production.
- Document the rollback in the runbook; test the procedure in staging at least once per quarter.
- Review structured logs with correlation IDs to trace requests end-to-end during incidents.
Key Takeaways
- Apply observer pattern when you need a practical solution for your use case.
- Monitor performance after implementation; measure latency, errors, and resource usage before and after.
- Check the Troubleshooting section for common failures; most have documented root causes with fixes.
- Keep dependencies updated and run tests in CI to prevent production regressions.
Advanced Topics
Scenario: Event System with Observer Pattern
// Observer pattern for notification system
interface Observer {
update(event: string, data: unknown): void;
}
class EventEmitter {
private observers: Map<string, Set<Observer>> = new Map();
subscribe(event: string, observer: Observer): void {
if (!this.observers.has(event)) {
this.observers.set(event, new Set());
}
this.observers.get(event)!.add(observer);
}
unsubscribe(event: string, observer: Observer): void {
this.observers.get(event)?.delete(observer);
}
emit(event: string, data: unknown): void {
this.observers.get(event)?.forEach(obs => {
try {
obs.update(event, data);
} catch (err) {
console.error(`Observer error: ${err}`);
}
});
}
}
// Usage: e-commerce system
const emitter = new EventEmitter();
// Observers
class EmailNotifier implements Observer {
update(event: string, data: unknown): void {
if (event === "order.created") {
sendEmail((data as Order).userEmail, "Order confirmed");
}
}
}
class InventoryUpdater implements Observer {
update(event: string, data: unknown): void {
if (event === "order.created") {
decrementStock((data as Order).items);
}
}
}
class AnalyticsTracker implements Observer {
update(event: string, data: unknown): void {
trackEvent(event, data);
}
}
// Subscribe
emitter.subscribe("order.created", new EmailNotifier());
emitter.subscribe("order.created", new InventoryUpdater());
emitter.subscribe("order.created", new AnalyticsTracker());
// Emit
emitter.emit("order.created", { id: "123", userEmail: "user@example.com", items: [...] });
// Result: email sent, stock updated, analytics tracked
Lessons:
- Observer decouples emitter from receivers
- Each observer is independent: failure in one does not affect others
- Use Set to avoid duplicates
- Try-catch in emit: a broken observer does not break the event
- For distributed systems, use a message broker (RabbitMQ, Kafka)
- Memory leak: unsubscribe when the observer is no longer needed
### How do I prevent memory leaks with observers?
Always call unsubscribe when the observer is no longer needed. In React, use useEffect cleanup: subscribe on mount, unsubscribe on unmount. In Node.js, use WeakRef or clean up explicitly on shutdown. If observers grow without limit, use a Map with TTL or a max observers per event.
End of document. Review and update quarterly.
## Common Production Pitfalls
- Applying the pattern where no abstraction is needed, adding accidental complexity.
- Letting the pattern leak into unrelated modules and blur ownership boundaries.
- Over-engineering the first implementation instead of starting simple and measuring pain.
- Skipping contract tests, so refactors silently break consumers.
- Ignoring failure modes that the pattern does not cover.
- Using the pattern as a default instead of choosing the right tool for the current scale.
- Forgetting to document when to stop using the pattern and what replaces it.
- Missing observability around the pattern's performance and error propagation. Frequently Asked Questions
What is the difference between Observer and Pub/Sub?
Observer is a direct subject-observer relationship. Pub/Sub adds an event broker (Mediator) that decouples publishers from subscribers completely.
Is the Observer Pattern still relevant with modern reactive frameworks?
Yes. React hooks, RxJS, and Vue's reactivity system are all built on Observer concepts. For singleton event brokers, see Singleton.
How do I prevent memory leaks with observers?
Always provide an unsubscribe mechanism and call it in cleanup handlers or destructors.
Related Resources
Call a REST API: Python, JavaScript, Java & Go Examples
How to make HTTP requests to a REST API and handle the JSON response in Python, JavaScript, Java, and Go.
PatternSingleton Pattern
Ensure a class has only one instance and provide global access to it. A creational design pattern for controlled object creation.
PatternStrategy Pattern
Define a family of algorithms, encapsulate each one, and make them interchangeable. A behavioral design pattern for flexible behavior selection.
PatternPipes and Filters Pattern: Composable Data Pipelines
Chain processing steps with independent filters connected by pipes. A pattern for data transformation pipelines where each step is reusable and composable.
PatternBlackboard Pattern
A shared knowledge space where independent specialized modules collaborate to solve complex problems by contributing partial solutions.
PatternCircuit Breaker Pattern
Prevent cascading failures by stopping requests to failing services. An architectural pattern for resilient distributed systems.