On this page
Description
Observer is the behavioral pattern for "when this happens, tell whoever cares": a subject keeps a list of observers and notifies each one when something changes, without knowing what they do. It's the idea behind UI event listeners, framework events, signals, reactive streams and - between services - publish/subscribe messaging. This skill covers the pattern, push versus pull, and the lifecycle and failure problems every real event system runs into.
When an order is placed, several things must happen: send a confirmation email, update inventory, record analytics. The quick version calls each of them directly:
Java
class OrderService {
void placeOrder(Order order) {
save(order);
emailService.sendConfirmation(order);
inventoryService.reserve(order);
analytics.track("order_placed", order);
// next month: loyalty points... then fraud checks...
}
}
Python
class OrderService:
def place_order(self, order: Order) -> None:
self._save(order)
self._email_service.send_confirmation(order)
self._inventory_service.reserve(order)
self._analytics.track("order_placed", order)
# next month: loyalty points... then fraud checks...
OrderService now knows about every part of the system that cares about orders. Each new reaction means editing it, and a bug in analytics can break checkout.
We'll go from simple to more technical:
- The pattern: a subject, an observer interface, subscribe and notify.
- How notification works, step by step - including push versus pull.
- Where you'll see observers, from button listeners to Kafka.
- Leaks, changes during notification, failing observers - and choosing among the behavioral patterns.
The pattern
Observer lets an object (the subject, or publisher) keep a list of observers (subscribers) and notify all of them when something happens. The subject only knows the observer interface - never the concrete classes.
Java
interface OrderListener {
void onOrderPlaced(Order order);
}
class OrderService {
private final List<OrderListener> listeners = new CopyOnWriteArrayList<>();
void subscribe(OrderListener l) { listeners.add(l); }
void unsubscribe(OrderListener l) { listeners.remove(l); }
void placeOrder(Order order) {
save(order);
for (OrderListener l : listeners) l.onOrderPlaced(order);
}
}
orderService.subscribe(order -> emailService.sendConfirmation(order));
orderService.subscribe(inventoryService::reserve);
Python
from collections.abc import Callable
type OrderListener = Callable[[Order], None]
class OrderService:
def __init__(self):
self._listeners: list[OrderListener] = []
def subscribe(self, listener: OrderListener) -> None:
self._listeners.append(listener)
def unsubscribe(self, listener: OrderListener) -> None:
self._listeners.remove(listener)
def place_order(self, order: Order) -> None:
self._save(order)
for listener in list(self._listeners): # a snapshot: safe if one unsubscribes
listener(order)
order_service.subscribe(email_service.send_confirmation)
order_service.subscribe(inventory_service.reserve)
Adding loyalty points later is one new subscribe(...) call. OrderService never changes.
In Python an observer is usually just a callable - a function or a bound method like email_service.send_confirmation - so the "interface" is Callable[[Order], None] and there is no class to implement.
How it works, step by step
- Define an observer interface with one method per kind of event.
- The subject keeps a list of observers and offers
subscribe/unsubscribe. - When the event happens, the subject loops over the list and calls each observer.
- Observers decide for themselves what to do; the subject doesn't care.
- Push vs. pull: either send the data with the notification (
onOrderPlaced(order)), or just say "something changed" and let observers ask for what they need.
Where you'll see it
- UI event listeners:
button.addActionListener(...),element.addEventListener("click", ...). - Spring's
ApplicationEventPublisherwith@EventListenermethods. - In Python, Django's signals (
post_save.connect(handler)) and Qt's signals and slots. - Reactive libraries (
java.util.concurrent.Flow, RxJava, Reactor) are Observer with extra features. - At system scale, publish/subscribe messaging (Kafka topics, SNS/SQS) is the same idea between services.
Choosing among the behavioral patterns
| If you need to... | Reach for | It's about |
|---|---|---|
| Let many objects react when something happens, without the source knowing them | Observer (this lesson) | Who gets told about a change. |
| Swap one algorithm for another at runtime | Strategy | How a task is done - chosen from outside. |
| Turn a request into an object you can queue, log, undo or redo | Command | What to do, packaged for later. |
| Make an object behave differently depending on its current state | State | Behavior that changes itself as the object moves between states. |
| Fix the steps of an algorithm but let subclasses fill in some of them | Template Method | The skeleton stays, the details vary. |
Common mistakes
| Mistake | What happens | Do this instead |
|---|---|---|
| Observers that never unsubscribe | the subject keeps them alive forever (the "lapsed listener" leak) | unsubscribe when the observer's lifetime ends |
| Iterating the live list while observers (un)subscribe | ConcurrentModificationException, or an observer silently skipped | iterate a snapshot, or use CopyOnWriteArrayList |
| A plain loop with no error handling | one failing observer stops the rest | catch and log per observer, then continue |
| A slow observer called synchronously | checkout waits for analytics | deliver asynchronously (an executor or a queue) |
| Depending on notification order | breaks when someone subscribes in a different order | make the sequence explicit instead |
Check yourself
Try to answer before opening each one - recalling beats re-reading.
1. Why does the subject know only the observer interface, never the concrete classes?
Show answer
So a new reaction is just a new subscriber: the subject's code doesn't change, and it can't be broken by changes inside the classes that react.
2. A dialog subscribes to a long-lived service and never unsubscribes. What happens after many dialogs are opened and closed?
Show answer
Memory grows: the service still references every closed dialog, so none can be garbage-collected - the lapsed listener problem. Unsubscribe when the dialog closes.
3. Name two ways to make notification safe when observers subscribe or unsubscribe during delivery.
Show answer
Iterate over a snapshot (new ArrayList<>(listeners)), or keep the listeners in a CopyOnWriteArrayList, whose iterators never see concurrent changes.
Key takeaways
- The subject notifies observers through an interface - it never knows the concrete classes.
- New reactions are new subscribers; the publisher doesn't change.
- Always give observers a way to unsubscribe, or they leak.
- Iterate over a snapshot and isolate each observer's exceptions.
- Between services, the same idea becomes publish/subscribe messaging.