The Observer Pattern defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified automatically and updated.
Difficulty: ⭐ Easy
Category: Behavioral Pattern
Interview Frequency: ⭐⭐⭐⭐ Very Common
Imagine you have:
- A data object (e.g., stock price, user status, game state)
- Multiple objects that need to react when the data changes
- You want loose coupling—objects shouldn't know about each other
Without Observer, you'd:
- Call update methods manually on every dependent
- Tightly couple objects
- Forget to notify some observers
- Have brittle code that breaks when requirements change
Think of a newspaper subscription:
- Subject: Newspaper publisher
- Observers: Subscribers
- When a new issue is published, all subscribers are automatically notified
- Subscribers don't need to constantly check; they're pushed the news
✅ Use Observer Pattern when:
- Multiple objects need to react to state changes
- You don't know in advance how many observers you'll have
- Objects should be loosely coupled
- You need event-driven architecture
- You want to avoid tight coupling
❌ Don't use when:
- Only one or two dependents
- Observers are tightly related (use Strategy instead)
- Performance is critical (notification overhead)
See observer.ts for complete implementation.
- Subject/Observable: Maintains list of observers, notifies them
- Observer: Interface for receiving notifications
- ConcreteObserver: Implements Observer interface
- ConcreteSubject: Maintains state, notifies observers when changed
// Observer interface
interface Observer {
update(subject: Subject): void;
}
// Subject
class Subject {
private observers: Observer[] = [];
private state: string = "";
attach(observer: Observer): void {
this.observers.push(observer);
}
detach(observer: Observer): void {
this.observers = this.observers.filter((o) => o !== observer);
}
setState(state: string): void {
this.state = state;
this.notifyObservers();
}
getState(): string {
return this.state;
}
private notifyObservers(): void {
this.observers.forEach((observer) => observer.update(this));
}
}
// Concrete Observer
class ConcreteObserver implements Observer {
constructor(private name: string) {}
update(subject: Subject): void {
console.log(`${this.name} received update: ${subject.getState()}`);
}
}
// Usage
const subject = new Subject();
const obs1 = new ConcreteObserver("Observer 1");
const obs2 = new ConcreteObserver("Observer 2");
subject.attach(obs1);
subject.attach(obs2);
subject.setState("New State"); // Both observers notified✅ Loose Coupling: Subject doesn't know concrete observer types
✅ Dynamic Relationships: Attach/detach observers at runtime
✅ Reusable: Subject and observers can be reused independently
✅ Separation of Concerns: Subject only manages state; observers handle reactions
✅ One-to-Many: One subject can notify many observers
❌ Observers notified in unpredictable order
❌ Memory leaks if observers not detached
❌ Performance overhead with many observers
❌ Hard to debug (implicit dependencies)
Answer:
- Observer: Direct coupling between Subject and Observer
- Subject directly calls observer.update()
- Synchronous
- Example: EventEmitter.on()
subject.attach(observer); // Direct reference
subject.setState(newState); // Directly notifies- Pub-Sub: Decoupled through a message broker
- Publishers and subscribers don't know each other
- Can be asynchronous
- Example: Message queues, Event buses
// Publish
eventBus.publish("stateChanged", newState);
// Subscribe
eventBus.subscribe("stateChanged", (data) => {
// Handle
});Answer: Always detach observers when they're no longer needed:
class Observer implements IObserver {
subscribe(subject: Subject): void {
subject.attach(this);
}
unsubscribe(subject: Subject): void {
subject.detach(this);
}
destroy(): void {
// Cleanup
}
}
// Usage
const observer = new Observer();
observer.subscribe(subject);
// Later, when done
observer.unsubscribe(subject);
observer.destroy();Answer: Depends on use case:
Synchronous (Direct Observer):
- Simple, immediate updates
- Issues: Slow observer blocks others, tight coupling
notifyObservers(): void {
this.observers.forEach(o => o.update(this)); // Blocks until all done
}Asynchronous (Message Bus):
- Non-blocking, better performance
- Issues: Harder to debug, ordering issues
async notifyObservers(): Promise<void> {
await Promise.all(this.observers.map(o => o.update(this)));
}Best Practice: Use synchronous for simple cases, async with message bus for complex systems.
Answer: Mostly terminology:
- Observer: More formal, emphasizes pattern
- Listener: Common in event systems, implies listening for events
- Both are essentially the same pattern
// Observer terminology
interface Observer {
update(subject: Subject): void;
}
// Listener terminology
type EventListener<T> = (event: T) => void;Answer: Several approaches:
- Pull Model: Observer queries subject
update(subject: Subject): void {
const state = subject.getState();
}- Push Model: Subject sends data to observer
update(data: string): void {
console.log(data);
}
notifyObservers(): void {
this.observers.forEach(o => o.update(this.state));
}- Event Object: Send complete event
interface StateChangedEvent {
oldState: string;
newState: string;
timestamp: Date;
}
update(event: StateChangedEvent): void {
// Handle event
}- UI Frameworks: Reactive components, Vue/React hooks
- Event Systems: DOM events, Node.js EventEmitter
- Stock Market: Price changes → trader notifications
- Chat Systems: New message → all subscribers notified
- Game Development: Player actions → UI, sound, physics updates
- MVC/MVVM: Model changes → all views update
- Notification Systems: Email/SMS alerts
- Real-time Data: WebSocket updates to multiple clients
private observersMap = new WeakMap<Observer, boolean>();
attach(observer: Observer): void {
this.observersMap.set(observer, true);
}
detach(observer: Observer): void {
this.observersMap.delete(observer);
}interface PriorityObserver {
update(subject: Subject): void;
priority: number;
}
notifyObservers(): void {
const sorted = this.observers.sort((a, b) =>
b.priority - a.priority
);
sorted.forEach(o => o.update(this));
}notifyObservers(): void {
this.observers.forEach(observer => {
if (observer.isInterested(this.state)) {
observer.update(this);
}
});
}notifyObservers(): void {
this.observers.forEach(observer => {
try {
observer.update(this);
} catch (error) {
console.error(`Observer error: ${error}`);
// Don't let one observer break others
}
});
}class UserService {
updateUser(id: string, data: any) {
const user = this.getUser(id);
user.update(data);
// Manual notification - what if we forget?
emailService.sendUpdateEmail(user);
uiComponent.refresh(user);
analyticsService.track("user-updated", user);
// And we need to modify this every time!
}
}class UserService extends Subject {
updateUser(id: string, data: any) {
const user = this.getUser(id);
user.update(data);
this.notifyObservers(user); // All observers notified automatically
}
}
// Subscribe once
const emailObserver = new EmailObserver();
const uiObserver = new UIObserver();
const analyticsObserver = new AnalyticsObserver();
userService.attach(emailObserver);
userService.attach(uiObserver);
userService.attach(analyticsObserver);
// Now updates automatically notify all!
userService.updateUser("123", newData);// Easy to test—mock observers
const mockObserver = {
update: jest.fn(),
};
subject.attach(mockObserver);
subject.setState("new");
expect(mockObserver.update).toHaveBeenCalled();- Mediator: Encapsulates how set of objects interact
- Pub-Sub: Looser coupling than Observer
- Command: Can be used with Observer for queuing
- Strategy: Can work with Observer for dynamic behavior
Observer Pattern is essential for:
- Event-driven architecture
- Building reactive systems
- Decoupling components
- Maintaining separation of concerns
Master it by understanding when to use synchronous vs async, and always remember to detach observers to prevent leaks.
Key Takeaway: "When one object's state changes, tell everyone interested—without them asking."
Next: Try the Decorator Pattern →