Design Principle
Observer Pattern
Learn the Observer pattern: define a one-to-many dependency between objects. When one object changes state, all dependents are notified automatically.
Observer Pattern
Why This Matters
Think of the Observer pattern like a newsletter subscription. When you subscribe to a newsletter, you get notified whenever a new issue is published. You don't have to keep checking—the publisher notifies you. The Observer pattern does the same for code—when an object's state changes, it notifies all observers automatically.
This matters because objects often need to react to changes in other objects. A UI might need to update when data changes. A cache might need to invalidate when data is updated. The Observer pattern provides a clean way to handle these dependencies without tight coupling. The subject (publisher) doesn't need to know about observers (subscribers).
In interviews, when someone asks "How would you implement an event system?", they're testing whether you understand the Observer pattern. Do you know how to decouple publishers and subscribers? Can you implement a publish-subscribe system? Most engineers can't. They use direct method calls and wonder why the code is tightly coupled.
What Engineers Usually Get Wrong
Engineers often think "Observer pattern means always use events." But events add indirection and complexity. If you have a simple one-to-one relationship, a direct method call is fine. Use Observer when you have one-to-many relationships, when observers might change at runtime, or when you need to decouple components.
Engineers also don't understand memory leaks with observers. If an observer holds a reference to the subject, and the subject holds a reference to the observer, neither can be garbage collected. This creates a memory leak. Always remove observers when they're no longer needed, or use weak references.
How This Breaks Systems in the Real World
A service was using the Observer pattern for event notifications. Components subscribed to events but never unsubscribed. Over time, observers accumulated. When events were published, all observers (including dead ones) were notified. This wasted CPU and memory. The fix? Always unsubscribe when observers are no longer needed. Use weak references or automatic cleanup.
Another story: A service was using direct method calls for notifications. When the team needed to add logging or metrics to all notifications, they had to modify code in 20 places. The fix? Use Observer pattern. Centralize notification logic. Now adding logging or metrics requires changes in only one place.
What is the Observer Pattern?
Observer Pattern provides:
- Loose coupling: Subject doesn't know concrete observers
- Dynamic relationships: Add/remove observers at runtime
- Event notification: Notify multiple observers of changes
- Broadcast communication: One subject, many observers
Use cases:
- Model-View architecture (MVC)
- Event handling systems
- Publish-subscribe systems
- Real-time updates
- UI frameworks (React, Vue)
Structure
Subject (interface)
├─ attach(observer)
├─ detach(observer)
└─ notify()
ConcreteSubject
└─ state
└─ notify() (notifies all observers)
Observer (interface)
└─ update()
ConcreteObserver
└─ update() (reacts to state change)
Examples
Basic Observer Pattern
1// Observer interface2interface Observer {3 update(data: any): void;4}56// Subject interface7interface Subject {8 attach(observer: Observer): void;9 detach(observer: Observer): void;10 notify(): void;11}1213// Concrete subject14class NewsAgency implements Subject {15 private observers: Observer[] = [];16 news
Stock Market Observer
1// Observer interface2interface StockObserver {3 update(stock: Stock): void;4}56// Subject7class StockMarket implements Subject {8 private observers: StockObserver[] = [];9 private stocks: Map<string, Stock> = new Map();1011 attach(observer: StockObserver): void {12 this.observers.push(observer);13 }
Event System
1// Event type2type EventType = string;34// Event data5interface EventData {6 [key: string]: any;7}89// Observer (event listener)10interface EventListener {11 handle(eventType: EventType, data: EventData): void;12}1314// Event emitter (subject)15class EventEmitter {16 private listeners: Map<EventType, EventListener[]> = new Map();1718 eventType EventType listener EventListener
Common Pitfalls
- Memory leaks: Observers not detached. Fix: Always detach observers
- Update order: Order of updates matters. Fix: Document order, use priority
- Too many observers: Performance issues. Fix: Limit observers, batch updates
- Circular updates: Observer updates subject. Fix: Prevent circular updates
Interview Questions
Beginner
Q: What is the Observer pattern and when would you use it?
A:
Observer Pattern defines one-to-many dependency between objects. When subject changes, all observers are notified.
Key characteristics:
- One-to-many: One subject, many observers
- Loose coupling: Subject doesn't know concrete observers
- Dynamic: Add/remove observers at runtime
- Automatic notification: Observers notified automatically
Example:
1class Subject {2 private observers: Observer[] = [];34 attach(observer: Observer): void {5 this.observers.push(observer);6 }78 notify(): void {9 this.observers.forEach(obs => obs.update());10 }11}
Use cases:
- MVC architecture: Model notifies views
- Event systems: Event emitters/listeners
- UI frameworks: React, Vue reactivity
- Real-time updates: Stock prices, notifications
Benefits:
- Loose coupling: Subject and observers decoupled
- Flexibility: Add/remove observers dynamically
- Broadcast: Notify multiple observers
Intermediate
Q: Explain how the Observer pattern works. How do you handle observer lifecycle and prevent memory leaks?
A:
Observer Pattern Structure:
1. Subject:
1class Subject {2 private observers: Observer[] = [];34 attach(observer: Observer): void {5 this.observers.push(observer);6 }78 detach(observer: Observer): void {9 const index = this.observers.indexOf(observer);10 if (index > -1) {11 this.observers.splice(index, 1
2. Observer:
1interface Observer {2 update(data: any): void;3}
Preventing Memory Leaks:
1. Always Detach:
1class Component {2 private observer: Observer;34 constructor(subject: Subject) {5 this.observer = { update: this.handleUpdate.bind(this) };6 subject.attach(this.observer);7 }89 destroy(): void {10 // CRITICAL: Detach before destroying11 subject.detach(this.observer);12 }13}
2. Weak References:
1class Subject {2 private observers: WeakSet<Observer> = new WeakSet();3 // WeakSet allows garbage collection4}
3. Automatic Cleanup:
1class AutoDetachingObserver implements Observer {2 constructor(3 private subject: Subject,4 private callback: Function5 ) {6 subject.attach(this);7 }89 update(data: any): void {10 this.callback(data);11 }1213 destroy(): void {14 this.subject.detach(this);15 }
Senior
Q: Design an observer system for a real-time collaborative editor where multiple users can edit a document simultaneously. Handle conflict resolution, network synchronization, and ensure all clients stay in sync.
A:
1// Document change event2interface DocumentChange {3 type: "insert" | "delete" | "format";4 position: number;5 content?: string;6 timestamp: number;7 userId: string;8}910// Observer interface11interface DocumentObserver {12 onDocumentChange(change: DocumentChange): void;13 onConflict(change: DocumentChange, localChange: DocumentChange): void;14}1516// Subject (Document)17class CollaborativeDocument
Features:
- Observer pattern: Notify all clients of changes
- Conflict detection: Detect conflicting changes
- Conflict resolution: Resolve conflicts (last-write-wins, merge)
- Network sync: Sync changes across network
- Version control: Track document version
Failure Stories You'll Recognize
The Memory Leak: A service was using the Observer pattern for event notifications. Components subscribed to events but never unsubscribed. Over time, observers accumulated. When events were published, all observers (including dead ones) were notified. This wasted CPU and memory. The fix? Always unsubscribe when observers are no longer needed. Use weak references or automatic cleanup.
The Tight Coupling: A service was using direct method calls for notifications. When the team needed to add logging or metrics to all notifications, they had to modify code in 20 places. The fix? Use Observer pattern. Centralize notification logic. Now adding logging or metrics requires changes in only one place.
The Circular Update: A service was using Observer pattern, but observers were updating the subject, which triggered more notifications. This created an infinite loop. The system hung. The fix? Prevent circular updates. Don't update the subject from within an observer, or use a flag to prevent recursive notifications.
What Interviewers Are Really Testing
They want to hear you talk about Observer as a decoupling mechanism, not just a pattern. Junior engineers say "Observer pattern notifies observers of changes." Senior engineers say "Observer pattern decouples publishers and subscribers. Use it for one-to-many relationships and event-driven systems. Always unsubscribe to prevent memory leaks. Prevent circular updates."
When they ask "How would you implement an event system?", they're testing:
-
Do you understand publish-subscribe?
-
Can you implement Observer pattern?
-
Do you understand memory leaks and lifecycle management?
-
Observer pattern: One-to-many dependency, automatic notification
-
Loose coupling: Subject and observers decoupled
-
Dynamic: Add/remove observers at runtime
-
Use cases: MVC, event systems, real-time updates
-
Memory leaks: Always detach observers
-
Best practices: Use weak references, handle lifecycle, prevent circular updates
How InterviewCrafted Will Teach This
We'll teach this through real code problems, not abstract patterns. Instead of memorizing "Observer pattern notifies observers," you'll learn through scenarios like "why did our system run out of memory?"
You'll see how Observer helps you decouple components and build event-driven systems. When an interviewer asks "how would you implement an event system?", you'll think about Observer pattern, memory leaks, and lifecycle—not just "use events."
- SOLID Principles - Observer pattern follows Dependency Inversion Principle (depend on abstractions). Understanding SOLID helps understand Observer benefits.
- Command Pattern - Both patterns decouple components. Observer decouples publisher/subscriber, Command decouples invoker/receiver.
- Separation of Concerns - Observer pattern helps separate concerns by decoupling components. Understanding separation of concerns helps understand Observer benefits.
- DRY, KISS & YAGNI - Observer pattern can add complexity. Understanding DRY/KISS/YAGNI helps decide when Observer is appropriate.
- Proxy Pattern - Proxy controls access, Observer notifies of changes. Understanding both helps choose the right pattern.
Key Takeaways
Observer pattern: One-to-many dependency, automatic notification
Loose coupling: Subject and observers decoupled
Dynamic: Add/remove observers at runtime
Use cases: MVC, event systems, real-time updates
Memory leaks: Always detach observers
Best practices: Use weak references, handle lifecycle, prevent circular updates
Related Topics
SOLID Principles
Observer pattern follows Dependency Inversion Principle (depend on abstractions). Understanding SOLID helps understand Observer benefits.
Command Pattern
Both patterns decouple components. Observer decouples publisher/subscriber, Command decouples invoker/receiver.
Separation of Concerns
Observer pattern helps separate concerns by decoupling components. Understanding separation of concerns helps understand Observer benefits.
DRY, KISS & YAGNI
Observer pattern can add complexity. Understanding DRY/KISS/YAGNI helps decide when Observer is appropriate.
Proxy Pattern
Proxy controls access, Observer notifies of changes. Understanding both helps choose the right pattern.
Keep exploring
Principles work best in chorus. Pair this lesson with another concept and observe how your architecture conversations change.