Observer Pattern
The problem
You have an object whose state matters to other parts of the system, but you do not want it to know anything specific about those dependents. The naive approach is to call each dependent directly, which creates tight coupling: the subject needs to import and call every listener, adding a new subscriber requires changing the subject, and removing one requires hunting down the call site.
The Observer pattern solves this by introducing a subscription contract. The subject (also called the publisher or event source) maintains a list of observers and calls a single method on each when state changes. Observers register and deregister themselves. The subject never imports a concrete observer class. You can add, remove, or swap observers at runtime without touching the subject’s code.
Structure
classDiagram class Market { -observers: PriceObserver[] +subscribe(observer) +unsubscribe(observer) +setPrice(symbol, price) } class PriceObserver { <<interface>> +onPriceChange(event) } class Logger { +onPriceChange(event) } class AlertObserver { -threshold: number +onPriceChange(event) } PriceObserver <|-- Logger PriceObserver <|-- AlertObserver Market --> PriceObserver : notifiesWhen to use
- One object’s state change should trigger reactions in other objects, and you do not know how many or which ones at compile time.
- You want to avoid polling: instead of consumers checking for changes on a timer, the subject pushes updates as they happen.
- You are building an event bus, a reactive UI layer, or any publish/subscribe system.
- You need to decouple a domain object from logging, metrics, or notification side effects.
Implementation
All three implementations build a generic event emitter backed by a listener registry. TypeScript uses a generic Events type parameter (a record mapping event names to payload types) so misspelled event names and wrong payload shapes are caught at compile time. Python uses defaultdict to avoid key-existence boilerplate and identity comparison (is not) in off so two listeners that happen to do the same thing are not confused. Go uses an interface-based observer contract with a sync.RWMutex: Subscribe and Unsubscribe take an exclusive write lock, and SetPrice snapshots the observer slice under a read lock before iterating so a concurrent Subscribe cannot corrupt the loop.
Click Run TS to execute. First run downloads Babel (~400 KB, cached after that).
Click Run Python to execute. First run downloads Python (~10 MB, cached after that).
Click Run Go to execute. Runs via the Go Playground API.
Tradeoffs
| Pro | Con |
|---|---|
| Loose coupling: subject knows only the observer interface | Order of notification is undefined unless explicitly managed |
| Open/closed: add observers without changing the subject | Memory leaks if observers are not removed when done |
| Foundation for event-driven architecture | Cascading updates can be hard to trace |
| Go’s channels offer a natural alternative for same-process pub/sub | Push model sends all data; observers cannot ask for a subset |
Gotchas
- Remove observers when they are no longer needed. In browser JS, failing to call
removeEventListeneris the most common source of observer-related memory leaks. - Copy the observer slice before iterating in Go (or any concurrent context). A
Subscribecall during notification will corrupt the iteration. - In Python,
listener is not listener_refidentity comparison works for named functions. Lambdas create a new object each call, so you cannot remove a lambda you did not store a reference to. - Deep observer chains (A notifies B which notifies C) produce hard-to-debug causality. Keep notification shallow. Prefer choreography over cascades.
- If two observers modify shared state in response to the same event, the interaction depends on notification order, which is insertion order by default. Make that dependency explicit or eliminate the shared state.
References
- Design Patterns: Observer, GoF, the canonical definition
- Refactoring Guru: Observer, diagrams and examples in multiple languages
- Node.js EventEmitter docs, the standard library implementation for reference
- MDN: EventTarget, the browser DOM variant
Related topics
- Design Patterns, the full GoF catalog
- Command, another behavioral pattern for decoupling action from invocation
- Strategy, swappable behaviors that complement Observer for flexible systems
- Functional Core, Imperative Shell, pairs well here: keep observer callbacks in the imperative shell, pure computations in the core