Skip to content
FollowtheDamnCode

Observer

Let any number of objects subscribe to an event and be told when it happens, without the publisher knowing who they are.

Big idea

Observer lets a subject keep a list of subscribers and notify all of them when something happens. The subject knows only that subscribers have a notify method — not what they do — so you can add reactions without editing the code that triggers them.

The problem it solves

When an order ships, the system must: email the customer, send an SMS, update the analytics dashboard, and tell the warehouse to restock. Written directly in Order.ship():

self.mailer.send(...)
self.sms.send(...)
self.analytics.track(...)
self.warehouse.restock(...)

Order now depends on four unrelated services, can't be tested without them, and every new reaction (loyalty points next quarter) is another edit to core order logic.

Think of it like

A newsletter. You subscribe; the writer publishes; everyone on the list gets the issue. The writer doesn't phone each reader or know what they'll do with it — one forwards it, one files it, one reads it on the train. Readers can unsubscribe anytime and the writer never changes how they publish.

Structure

The subject holds subscribers behind one interface
One publish, many reactions

In code

Order events with plug-in reactions
from collections import defaultdict

class EventBus:                                  # the subject, generalised to topics
    def __init__(self):
        self._subs = defaultdict(list)
    def subscribe(self, topic, handler):
        self._subs[topic].append(handler)
        return lambda: self._subs[topic].remove(handler)   # call to unsubscribe
    def publish(self, topic, payload):
        for handler in list(self._subs[topic]):
            handler(payload)


class Order:
    def __init__(self, bus, oid, customer):
        self.bus, self.oid, self.customer, self.status = bus, oid, customer, "paid"
    def ship(self):
        self.status = "shipped"
        self.bus.publish("order.shipped", self)     # knows no subscribers


bus = EventBus()
bus.subscribe("order.shipped", lambda o: print(f"  email {o.customer}: order {o.oid} is on its way"))
stop_sms = bus.subscribe("order.shipped", lambda o: print(f"  sms {o.customer}: shipped"))
shipped = []
bus.subscribe("order.shipped", lambda o: shipped.append(o.oid))

print("ship 1:")
Order(bus, 1, "ana").ship()
stop_sms()                                       # ana turned off texts
print("ship 2:")
Order(bus, 2, "ana").ship()
print("analytics saw:", shipped)
output
ship 1:
  email ana: order 1 is on its way
  sms ana: shipped
ship 2:
  email ana: order 2 is on its way
analytics saw: [1, 2]

Run and edit it in the app.

Adding loyalty points next quarter is one more subscribe call at startup; Order is untouched.

Watch it

Push vs pull, sync vs async

  • Push — the event carries the data subscribers need. Pull — the event just says "I changed" and subscribers query the subject.
  • Synchronous (above) — subscribers run inside publish; a slow or failing subscriber slows or breaks the publisher. Isolate failures (try/except per handler) or go asynchronous with a queue — the system-design publish–subscribe model.

In Observer, what does the subject know about its subscribers?

Predict: who hears the second event?
subs = []
def publish(msg):
    for s in list(subs):
        s(msg)

def once(msg):
    print("once got", msg)
    subs.remove(once)

subs.append(once)
subs.append(lambda m: print("always got", m))
publish("A")
publish("B")
What does it print? Show the answer
once got A
always got A
always got B

Run and edit it in the app.

When to use it — and when not to

Use it when one change must trigger an open-ended set of reactions: UI updates from a model, domain events, plug-in hooks, cache invalidation, notifications. Be careful when order matters between subscribers, when you need a reply (that's a request, not an event), or when chains of events cause hard-to-trace cascades.

Pros and cons

ProsCons
Publisher decoupled from reactions (Open/Closed)Notification order is usually unspecified
Subscribe and unsubscribe at runtimeForgotten subscriptions leak memory and do surprising work
Natural fit for UIs and domain eventsEvent cascades are hard to debug

How it relates

A screen subscribes to price updates but never unsubscribes when it closes. What happens?

Key terms

  • Observer — A pattern where subscribers are notified automatically when a subject publishes an event.
  • Subject (publisher) — The object that keeps a subscriber list and announces changes.
  • Subscriber (observer) — An object registered to be notified of events.
  • Lapsed listener — A subscriber that was never removed, causing leaks and unwanted work.