Design Patterns: Behavioral · Behavioral Patterns

Observer Pattern

Letting subscribers react whenever a subject's state changes, without the subject knowing who they are.

  • 5 min read
  • 11 practice questions
  • 2 graded problems
On this page
  1. Description
  2. The pattern
  3. How it works, step by step
  4. Where you'll see it
  5. Choosing among the behavioral patterns
  6. Common mistakes
  7. Check yourself
  8. Key takeaways
  9. Quick revise
  10. Practice

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:

  1. The pattern: a subject, an observer interface, subscribe and notify.
  2. How notification works, step by step - including push versus pull.
  3. Where you'll see observers, from button listeners to Kafka.
  4. 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

  1. Define an observer interface with one method per kind of event.
  2. The subject keeps a list of observers and offers subscribe / unsubscribe.
  3. When the event happens, the subject loops over the list and calls each observer.
  4. Observers decide for themselves what to do; the subject doesn't care.
  5. 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 ApplicationEventPublisher with @EventListener methods.
  • 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 forIt's about
Let many objects react when something happens, without the source knowing themObserver (this lesson)Who gets told about a change.
Swap one algorithm for another at runtimeStrategyHow a task is done - chosen from outside.
Turn a request into an object you can queue, log, undo or redoCommandWhat to do, packaged for later.
Make an object behave differently depending on its current stateStateBehavior that changes itself as the object moves between states.
Fix the steps of an algorithm but let subclasses fill in some of themTemplate MethodThe skeleton stays, the details vary.

Common mistakes

MistakeWhat happensDo this instead
Observers that never unsubscribethe subject keeps them alive forever (the "lapsed listener" leak)unsubscribe when the observer's lifetime ends
Iterating the live list while observers (un)subscribeConcurrentModificationException, or an observer silently skippediterate a snapshot, or use CopyOnWriteArrayList
A plain loop with no error handlingone failing observer stops the restcatch and log per observer, then continue
A slow observer called synchronouslycheckout waits for analyticsdeliver asynchronously (an executor or a queue)
Depending on notification orderbreaks when someone subscribes in a different ordermake 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.

Quick revise

The whole skill on one page, for the night before an interview.

Everything from the lesson and the deep dive on one page. Skim it before practice or an interview, and open the full lesson for anything that doesn't ring a bell.

Pick the tool

If you need to...Reach for
Notify many objects of a changeObserver
Swap algorithms at runtimeStrategy
Queue, log or undo requestsCommand
Behavior that follows the object's stateState
A fixed recipe with blanksTemplate Method
Notify other servicespublish/subscribe messaging (with an outbox)

Snippets worth knowing by heart

Java

private final List<OrderListener> listeners = new CopyOnWriteArrayList<>();

void placeOrder(Order order) {
    save(order);
    for (OrderListener l : listeners) {          // a snapshot: safe to (un)subscribe during delivery
        try {
            l.onOrderPlaced(order);
        } catch (RuntimeException e) {
            log.warn("listener failed", e);       // one bad observer doesn't stop the rest
        }
    }
}

Python

def place_order(self, order: Order) -> None:
    self._save(order)
    for listener in list(self._listeners):      # a snapshot
        try:
            listener(order)
        except Exception:
            log.exception("listener failed")    # keep going

The traps

  • Never unsubscribing - lapsed-listener leaks.
  • Iterating the live list - exceptions or silent skips.
  • One failing observer stops the loop.
  • Slow synchronous observers.
  • Order dependence between observers.

Staff-level points, one line each

  • Removal during iteration: Java CME (or a silent skip when removing the second-to-last); Python always skips the next observer.
  • Weak registries drop listeners nobody else holds: a Java lambda after GC, a Python bound method immediately - use WeakMethod.
  • Re-entrant publishing reorders events (l2 saw B before A); queue events during dispatch.
  • Events delivered before commit act on rolled-back data - after-commit listeners or an outbox.
  • Async delivery is at-least-once: idempotent observers, sequence numbers, durable queues.
  • Backpressure: bounded buffers, Flow request(n), asyncio.Queue(maxsize=...).

Say it in an interview

  • "Checkout publishes OrderPlaced; email, inventory and analytics subscribe - checkout knows none of them."
  • "Notification iterates a snapshot and isolates each observer's failures."
  • "Side effects run after the transaction commits, via an outbox."
  • "Consumers dedupe by event id, because delivery is at-least-once."

Practice

This skill comes with 11 multiple-choice questions that tell you which misconception a wrong answer reveals and 2 graded problems. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice Observer Pattern free

Stock Price Alerts via the Observer Pattern

MediumProCoding problemJava · Python

A Stock starts at initialPrice. It receives a sequence of price updates. An AlertObserver, subscribed to the stock, should record "ALERT" for a price update if the absolute change from the previous price is >= threshold, and "OK" otherwise - without Stock itself containing any…

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Make an Event Bus Survive Real Subscribers

MediumProCoding problemJava · Python

This EventBus works for well-behaved subscribers, but real ones misbehave. Its publish loop is the naive version:

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Practice Observer Pattern free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime