Low-Level Design · lesson 16 of 18
Beginner
4Intermediate
10- 5 Design a Parking Lot: Allocating and Freeing Spots
- 6 Design an Elevator System: Scheduling Requests
- 7 Design a Library Management System: Borrowing and Returning Books
- 8 Design an Expense-Splitting App: Balances and Minimal Settlements
- 9 Design a URL Shortener: Sequential Codes vs Real Hash Collisions
- 10 Wrapping Objects: Adapter, Facade, and Proxy in Python
- 11 The Composite Pattern in Python: Sizing a File Tree Without isinstance
- 12 The State Pattern in Python: Refactoring a Vending Machine Into State Classes
- 13 Undo and Redo in Python: the Memento and Command Patterns Compared
- 14 Chain of Responsibility in Python: Expense Approvals Without an if/elif Ladder
Advanced
4- 15 Concurrency in LLD: A Real Race Condition in the Parking Lot, Then the Fix
- 16 The Observer Pattern in Practice: A Stock Ticker Notifying Multiple Displays
- 17 Design Patterns That Are Just Language Features: Strategy, Command, Iterator
- 18 Abstract Factory and Prototype in Python: Matching Families and Cloning Objects
Also part of Low-Level Design
12- + SOLID Principles in Java from Java
- + Composition vs Inheritance in Java from Java
- + Design Patterns in Java from Java
- + Python Design Patterns from Python
- + Design Patterns in TypeScript from TypeScript
- + JavaScript Design Patterns from JavaScript
- + Design Patterns in C# from C#
- + Design Patterns in C++ from C++
- + Design Patterns in Go from Go
- + Design Patterns in Rust from Rust
- + Abstract Classes in Java from Java
- + Sealed Classes and Interfaces in Java from Java
The Observer Pattern in Practice: A Stock Ticker Notifying Multiple Displays
AlertDisplay is fully coded but never called — the naive ticker forgot to wire it in. The Observer pattern makes that omission structurally impossible.
By SkillByExample · Updated
Two displays work fine hardcoded by name. The third one is where the naive version breaks.
The naive version: hardcoded display calls
class PriceDisplay:
def show(self, price):
print(f"[PriceDisplay] Current price: {price}")
class ChartDisplay:
def show(self, price):
print(f"[ChartDisplay] Plotting point: {price}")
class AlertDisplay:
def show(self, price):
if price > 100:
print(f"[AlertDisplay] ALERT: price {price} exceeds 100")
class NaiveStockTicker:
def __init__(self):
self.price_display = PriceDisplay()
self.chart_display = ChartDisplay()
# AlertDisplay exists but nobody wired it in
def set_price(self, price):
self.price_display.show(price)
self.chart_display.show(price)
ticker = NaiveStockTicker()
ticker.set_price(101.5)
$ python naive_ticker.py
[PriceDisplay] Current price: 101.5
[ChartDisplay] Plotting point: 101.5
AlertDisplay is fully implemented and would correctly fire on a price over 100 — but
NaiveStockTicker.__init__ never constructs one, and set_price never calls it. This is the
same silent-omission bug from the Open/Closed lesson earlier in this track, wearing a
different domain: the ticker has to be told about every display class by name, and one got
missed.
The fix: the Observer pattern
class StockTicker:
def __init__(self):
self._observers = []
def subscribe(self, observer):
self._observers.append(observer)
def set_price(self, price):
for observer in self._observers:
observer.update(price)
class PriceDisplay:
def update(self, price):
print(f"[PriceDisplay] Current price: {price}")
class ChartDisplay:
def update(self, price):
print(f"[ChartDisplay] Plotting point: {price}")
class AlertDisplay:
def update(self, price):
if price > 100:
print(f"[AlertDisplay] ALERT: price {price} exceeds 100")
ticker = StockTicker()
ticker.subscribe(PriceDisplay())
ticker.subscribe(ChartDisplay())
ticker.subscribe(AlertDisplay())
ticker.set_price(101.5)
$ python observer_ticker.py
[PriceDisplay] Current price: 101.5
[ChartDisplay] Plotting point: 101.5
[AlertDisplay] ALERT: price 101.5 exceeds 100
Same three displays, same price — but this time AlertDisplay fires, because being included
is just a matter of calling ticker.subscribe(AlertDisplay()) once. StockTicker itself
never changed at all between the two-display and three-display versions.
Practice
1. Add an unsubscribe method and confirm it stops future notifications.
class StockTicker:
def __init__(self):
self._observers = []
def subscribe(self, observer):
self._observers.append(observer)
def unsubscribe(self, observer):
self._observers.remove(observer)
def set_price(self, price):
for observer in self._observers:
observer.update(price)
class PriceDisplay:
def update(self, price):
print(f"[PriceDisplay] Current price: {price}")
ticker = StockTicker()
display = PriceDisplay()
ticker.subscribe(display)
ticker.set_price(100)
ticker.unsubscribe(display)
ticker.set_price(200)
print("done")
$ python unsubscribe.py
[PriceDisplay] Current price: 100
done
The second set_price(200) call produces no display output at all — _observers is empty
after unsubscribe, so the loop inside set_price has nothing to iterate over.
2. Confirm subscribing the same observer twice causes duplicate notifications.
class StockTicker:
def __init__(self):
self._observers = []
def subscribe(self, observer):
self._observers.append(observer)
def set_price(self, price):
for observer in self._observers:
observer.update(price)
class PriceDisplay:
def update(self, price):
print(f"[PriceDisplay] Current price: {price}")
ticker = StockTicker()
display = PriceDisplay()
ticker.subscribe(display)
ticker.subscribe(display)
ticker.set_price(100)
$ python double_subscribe.py
[PriceDisplay] Current price: 100
[PriceDisplay] Current price: 100
_observers is a plain list, so the same object can be appended twice — this is a real bug a
production subscribe() should guard against, most simply by checking if observer not in self._observers before appending.
3. Add a brand-new observer type, confirming StockTicker needs zero changes.
class StockTicker:
def __init__(self):
self._observers = []
def subscribe(self, observer):
self._observers.append(observer)
def set_price(self, price):
for observer in self._observers:
observer.update(price)
class LoggingDisplay:
def update(self, price):
print(f"[LoggingDisplay] logged price={price}")
ticker = StockTicker()
ticker.subscribe(LoggingDisplay())
ticker.set_price(150)
$ python new_observer.py
[LoggingDisplay] logged price=150
LoggingDisplay was never seen by StockTicker before this script — it works purely because
it implements update(price), which is the entire contract subscribe/set_price depend on.
The next lesson, Design Patterns That Are Just Language Features, shows Strategy, Command and Iterator collapsing into functions, closures and generators.