software-design/the-observer-pattern-how-a-shop-tells-many-functions-about-one-order-w.md
The observer pattern: how a shop tells many functions about one order without knowing them
A shop that must email a receipt, update stock and add loyalty points ends up with one function that knows about everything. Every new request means editing it again.
The observer pattern flips this around: the shop keeps a list of functions and simply calls whatever is on it. Subscribe and unsubscribe listeners in the interactive, place an order, and watch the shop's own code stay untouched. You also get real Python output, plus the catches: one failing listener can stop the rest, and forgetting to unsubscribe leaves old functions running.

You build a little online shop. When an order is placed, three things must happen: email a receipt, lower the stock count, and add loyalty points. The obvious code puts all three calls inside place_order. It works, until the day someone asks for a fourth thing, and then a fifth. Every request means opening the same function again. How can the shop announce "an order happened" without knowing who cares?
The obvious way: the shop calls everyone
Here is the first version. A function is a named block of code you can run by calling its name; a class is a bundle of such functions that belong together.
def send_receipt(item):
print(" email: receipt for", item)
def update_stock(item):
print(" stock: one less", item)
class Shop:
def place_order(self, item):
print("order placed:", item)
send_receipt(item) # Shop must know about every follower
update_stock(item)
Shop().place_order("mug")
Running it prints:
order placed: mug
email: receipt for mug
stock: one less mug
Nothing is wrong with the output. The trouble is the shape: Shop has to know the name of every function that reacts to an order. Programmers call that coupling: two pieces of code are coupled when changing one forces you to change the other. Add "print a shipping label" and you must edit Shop, which now has to be re-tested, even though nothing about taking an order changed.
The fix: keep a list of listeners
Flip the relationship. Instead of the shop knowing its followers, the followers tell the shop "call me when something happens". The shop only keeps a list of functions and calls whatever is in it. This idea is called the observer pattern, or publish and subscribe: the shop is the publisher, each function on the list is a subscriber (also called a listener), and an announcement is an event.
class Shop:
def __init__(self):
self.listeners = [] # who wants to hear about orders
def subscribe(self, fn):
self.listeners.append(fn)
def unsubscribe(self, fn):
self.listeners.remove(fn)
def place_order(self, item):
print("order placed:", item)
for fn in list(self.listeners): # copy: listeners may unsubscribe while we loop
fn(item)
In Python a function is a value, so subscribe(send_receipt) simply stores the function in a list. Now try it yourself below. Tick a box to subscribe a listener, untick to unsubscribe, then place an order. The shop's own code never changes.
Running the real thing
Here is the rest of the Python, using the Shop above:
def send_receipt(item):
print(" email: receipt for", item)
def update_stock(item):
print(" stock: one less", item)
shop = Shop()
shop.subscribe(send_receipt)
shop.subscribe(update_stock)
shop.place_order("mug")
shop.unsubscribe(send_receipt)
shop.place_order("lamp")
order placed: mug
email: receipt for mug
stock: one less mug
order placed: lamp
stock: one less lamp
After unsubscribe, the second order no longer sends a receipt, and Shop did not need a single edit. That is the payoff: the publisher and its subscribers are loosely coupled. They agree on one small thing, the shape of the call (here, "a function that takes the item"), and nothing else.
The catches worth knowing
One broken listener can block the others
The loop calls listeners one after another. If one raises an error, the loop stops, and later listeners never hear the event. Here broken comes first in the list:
def broken(item): raise RuntimeError("mail server down")
shop.listeners += [broken, update_stock]
try:
shop.place_order("mug")
except RuntimeError as e:
print("order failed:", e)
order failed: mail server down
update_stock never ran, and the order itself reported a failure caused by an email problem. Real systems often wrap each call in its own try so one failure is recorded but does not stop the rest, or they hand the event to a queue and process it later. Which choice is right depends on whether the listener is essential (take payment) or optional (send a chat message).
Order and hidden effects
Listeners usually run in the order they subscribed, but code that relies on that order is fragile. And because the shop does not know who is listening, a bug can be hard to trace: reading place_order no longer tells you everything that happens when an order is placed. Good names and one place where subscriptions are set up keep this manageable.
Forgetting to unsubscribe
The list holds on to each function. A page or screen that subscribes and never unsubscribes can keep running, and keep using memory, long after the user has left it. Always pair subscribe with unsubscribe.
Where you have already seen this
The same shape is everywhere. A button click in a browser calls the functions you registered with addEventListener. A chat app notifies every open window when a message arrives. Many frameworks have "events" or "signals" for the same job. The names change, but the idea is always a list of functions and a loop.
So what for your own code?
When you notice one function growing a long list of "and then also do this" calls, ask: does the caller really need to know about all of these? If the answer is no, give it a list of listeners instead. If there are only two followers and they will never change, the plain calls are simpler, and that is fine. The pattern earns its place when the list of reactions keeps growing, or when it differs between situations such as tests and the real app.