A parking lot has one electronic display board showing free spots. The first design is simple: ParkingLot keeps a direct reference to DisplayBoard, and calls display.update(spot_count) every time a car parks or leaves.
Then: "We're adding a mobile app that also needs live updates. And a security dashboard next quarter."
If ParkingLot needs a new direct reference for every new thing that wants to know about spot changes, you've found today's pain — and Observer is the fix.
The pain: hard-coded listeners
class ParkingLot:
def __init__(self):
self.display = DisplayBoard() # direct reference #1
def on_spot_change(self, spot_count):
self.display.update(spot_count)
# adding mobile app support = adding reference #2 here
# adding security dashboard = adding reference #3 here...Every new kind of listener means editing ParkingLot itself — the same Open/Closed pressure you've now seen twice before (Factory, Strategy). This time the varying thing isn't which class to create or which algorithm to run — it's who needs to be told when something happens.
The pattern: Subject and Observer
Instead of direct references, ParkingLot keeps a list of anything implementing an Observer interface — it never needs to know the concrete class of any of them:
class Observer:
def update(self, spot_count): raise NotImplementedError
class ParkingLot:
def __init__(self):
self._observers = []
def attach(self, observer: Observer):
self._observers.append(observer)
def detach(self, observer: Observer):
self._observers.remove(observer)
def notify_observers(self, spot_count):
for observer in self._observers:
observer.update(spot_count)
def on_spot_change(self, spot_count):
self.notify_observers(spot_count)
class DisplayBoard(Observer):
def update(self, spot_count):
print(f"Display: {spot_count} spots free")
class MobileAppNotifier(Observer):
def update(self, spot_count):
print(f"Push notification: {spot_count} spots free")lot = ParkingLot()
lot.attach(DisplayBoard())
lot.attach(MobileAppNotifier())
lot.on_spot_change(42)
# both observers get notified — ParkingLot's code never mentions either class by nameAdding the security dashboard later is just one new class implementing Observer, plus one attach() call — ParkingLot itself doesn't change. RefactoringGuru's definition: Observer "lets you define a subscription mechanism to notify multiple objects about any events that happen to the object they're observing."
Push vs pull notification
The example above pushes the changed data directly as an argument to update(). This is simple, but ties every observer's interface to that exact data shape. The alternative, pull, sends only a "something changed" signal, and each observer calls back into the subject for exactly what it needs:
class Observer:
def update(self, subject): # gets the subject, not the raw data
raise NotImplementedError
class DisplayBoard(Observer):
def update(self, subject):
count = subject.get_spot_count() # pulls only what it needs
print(f"Display: {count} spots free")Pull is more flexible when different observers need different pieces of state; push is simpler when everyone needs the same small piece of data. Either is a reasonable interview answer — the trade-off is worth stating out loud.
The memory-leak trap
detach()-ed, even after it's no longer needed. The subject keeps calling update() on a "dead" observer forever, and that observer can't be garbage collected because the subject still holds a reference to it. Always pair every attach() with a matching detach() in a cleanup path.When Observer is overkill
If there is exactly one listener, permanently, with no realistic second one coming — a direct method call is simpler and there's no subscription pain to solve. Observer earns its place once you can genuinely say "more than one thing needs to know, and that list can grow."
Common mistakes
- Forgetting
detach(), leaking observers that should have been removed. - Notifying observers in an order the code silently depends on (avoid assuming order matters unless the design explicitly requires it).
- Coupling the observer interface too tightly to one specific data shape when different observers actually need different data (a sign to consider pull instead of push).
- Adding Observer for a single, permanent listener that will never have a second subscriber.
Quick recap
| Idea | One-liner |
|---|---|
| Pain solved | A subject that must notify a growing, changing set of dependents. |
| Structure | Subject holds a list of Observer interfaces; attach()/detach()/notify(). |
| Push | Send the changed data directly — simple, less flexible. |
| Pull | Send a signal; observer fetches what it needs — flexible, more overhead. |
| Trap | Forgotten detach() calls leak dead observers. |
One thing to remember
The moment a subject needs to tell more than one, possibly growing, set of dependents about a change, stop hard-coding references — hold a list of an Observer interface instead.
Practice Zone
Five MCQs, then two reasoning questions.
What problem does the Observer pattern solve?
Asked in


What two methods does the Subject typically expose, besides notify()?
Asked in

A display board subscribes to a ParkingLot's updates when the app starts, but is never explicitly detached even after the display is closed. What's the risk?
Asked in

In the 'push' style of Observer notification, what does the subject send to each observer?
Asked in

Which phrase is the strongest Observer signal?
Asked in


Think it through, then reveal:
A parking lot needs to update an electronic display board whenever a spot is taken or freed. The first design has ParkingLot hold a direct reference to DisplayBoard and call display.update(spot_count) after every parking event. The interviewer says: "Now add a mobile app that also needs live updates." What breaks, and how does Observer fix it?
Asked in

When would you prefer 'pull' notification over 'push' in an Observer implementation?
Asked in

FAQ
Is Observer the same idea as pub-sub (publish-subscribe)?
Closely related, with one common distinction: in classic Observer, the subject calls each observer directly — they know about each other's interface. Pub-sub usually adds a broker/message-queue in between, so publishers and subscribers don't need any direct reference to each other at all. For an LLD interview, describing in-process Observer is almost always what's expected unless the question explicitly mentions messaging infrastructure.
What if an observer's update() throws an exception?
Worth raising as a design consideration: should one failing observer stop the rest from being notified? A common, safer approach wraps each observer's update() call so one failure is logged and skipped rather than blocking notification to everyone else.
Can an object be both a Subject and an Observer at the same time?
Yes — a component can subscribe to one subject and, in turn, notify its own observers when it processes that update. This chains naturally and is common in real event-driven systems.
Observer connected many listeners to one subject. Next, we look at connecting two interfaces that were never designed to work together — Lesson 7: Adapter Pattern →


