Skip to content

Observer Pattern

Stay updated automatically - when one object changes, all observers get notified!

Observer Pattern: Keeping Everyone in the Loop

Section titled “Observer Pattern: Keeping Everyone in the Loop”

Now let’s dive into the Observer Pattern - one of the most commonly used behavioral design patterns.

Imagine you’re subscribed to a YouTube channel. When the creator uploads a new video, you automatically get notified - you don’t have to keep checking! The Observer Pattern works the same way!

The Observer Pattern lets objects notify multiple other objects (observers) about state changes automatically. When the subject (the thing being watched) changes, all observers are notified without the subject needing to know who’s watching.

The Observer Pattern is useful when:

  1. One object changes and multiple objects need to be notified
  2. You want loose coupling - Subject doesn’t need to know about observers
  3. You need dynamic relationships - Observers can be added/removed at runtime
  4. You want to avoid polling - No need to constantly check for changes
  5. You need one-to-many dependency - One subject, many observers

What Happens If We Don’t Use Observer Pattern?

Section titled “What Happens If We Don’t Use Observer Pattern?”

Without the Observer Pattern, you might:

  • Tightly couple objects - Subject needs to know all observers
  • Use polling - Constantly checking for changes (inefficient)
  • Scatter notification logic - Update code spread everywhere
  • Make it hard to add/remove observers - Need to modify subject code
  • Violate Open/Closed Principle - Need to modify code to add observers

Let’s start with a super simple example that anyone can understand!

You’re building a weather monitoring system. When the temperature changes, you need to update multiple displays (phone app, website, billboard). Without Observer Pattern:

bad_weather.py
# ❌ Without Observer Pattern - Tight coupling
class WeatherStation:
def __init__(self):
self.temperature = 0
# Problem: Subject knows about all observers!
self.phone_app = None
self.website = None
self.billboard = None
def set_temperature(self, temp: float):
self.temperature = temp
# Problem: Need to manually notify each observer
if self.phone_app:
self.phone_app.update(temp)
if self.website:
self.website.update(temp)
if self.billboard:
self.billboard.update(temp)
# Every time you add a new display, you need to modify this!
class PhoneApp:
def update(self, temperature: float):
print(f"📱 Phone: Temperature is {temperature}°C")
class Website:
def update(self, temperature: float):
print(f"🌐 Website: Temperature is {temperature}°C")
class Billboard:
def update(self, temperature: float):
print(f"📺 Billboard: Temperature is {temperature}°C")
# Usage
station = WeatherStation()
station.phone_app = PhoneApp()
station.website = Website()
station.billboard = Billboard()
station.set_temperature(25.0)

Problems:

  • WeatherStation knows about all display types (tight coupling)
  • Need to modify set_temperature() every time you add a new display
  • Hard to remove observers
  • Violates Open/Closed Principle

Problems:

  • WeatherStation knows about all display types (tight coupling)
  • Need to modify set_temperature() every time you add a new display
  • Hard to remove observers
  • Violates Open/Closed Principle
classDiagram
    class Subject {
        <<abstract>>
        +attach(observer) void
        +detach(observer) void
        +notify() void
    }
    class Observer {
        <<abstract>>
        +update(temperature) void
    }
    class WeatherStation {
        -temperature: float
        -observers: List~Observer~
        +attach(observer) void
        +detach(observer) void
        +notify() void
        +set_temperature(temp) void
        +get_temperature() float
    }
    class PhoneApp {
        +update(temperature) void
    }
    class Website {
        +update(temperature) void
    }
    class Billboard {
        +update(temperature) void
    }
    
    Subject <|-- WeatherStation : implements
    Observer <|-- PhoneApp : implements
    Observer <|-- Website : implements
    Observer <|-- Billboard : implements
    WeatherStation --> Observer : notifies
    
    note for WeatherStation "Maintains list of observers<br/>Notifies all on state change"
    note for Observer "All observers implement<br/>the same interface"
weather_observer.py
from abc import ABC, abstractmethod
from typing import List
# Step 1: Define the Observer interface
class Observer(ABC):
"""Interface for all observers"""
@abstractmethod
def update(self, temperature: float) -> None:
"""Called when subject's state changes"""
pass
# Step 2: Define the Subject interface
class Subject(ABC):
"""Interface for subjects that can be observed"""
@abstractmethod
def attach(self, observer: Observer) -> None:
"""Attach an observer"""
pass
@abstractmethod
def detach(self, observer: Observer) -> None:
"""Detach an observer"""
pass
@abstractmethod
def notify(self) -> None:
"""Notify all observers"""
pass
# Step 3: Create concrete Subject
class WeatherStation(Subject):
"""Weather station - the subject being observed"""
def __init__(self):
self._temperature = 0
self._observers: List[Observer] = [] # List of observers
def attach(self, observer: Observer) -> None:
"""Subscribe an observer"""
if observer not in self._observers:
self._observers.append(observer)
print(f"✅ Observer attached: {observer.__class__.__name__}")
def detach(self, observer: Observer) -> None:
"""Unsubscribe an observer"""
if observer in self._observers:
self._observers.remove(observer)
print(f"❌ Observer detached: {observer.__class__.__name__}")
def notify(self) -> None:
"""Notify all observers about temperature change"""
for observer in self._observers:
observer.update(self._temperature)
def set_temperature(self, temperature: float) -> None:
"""Set temperature and notify all observers"""
self._temperature = temperature
print(f"\n🌡️ Temperature changed to {temperature}°C")
self.notify() # Notify all observers!
def get_temperature(self) -> float:
"""Get current temperature"""
return self._temperature
# Step 4: Create concrete Observers
class PhoneApp(Observer):
"""Phone app observer"""
def update(self, temperature: float) -> None:
print(f"📱 Phone App: Temperature is {temperature}°C")
class Website(Observer):
"""Website observer"""
def update(self, temperature: float) -> None:
print(f"🌐 Website: Temperature is {temperature}°C")
class Billboard(Observer):
"""Billboard observer"""
def update(self, temperature: float) -> None:
print(f"📺 Billboard: Temperature is {temperature}°C")
# Step 5: Use the pattern
def main():
# Create subject
weather_station = WeatherStation()
# Create observers
phone_app = PhoneApp()
website = Website()
billboard = Billboard()
# Subscribe observers
weather_station.attach(phone_app)
weather_station.attach(website)
weather_station.attach(billboard)
# Change temperature - all observers get notified automatically!
weather_station.set_temperature(25.0)
# Unsubscribe one observer
weather_station.detach(website)
# Change temperature again - only subscribed observers get notified
weather_station.set_temperature(30.0)
# Add a new observer - no need to modify WeatherStation!
class SmartWatch(Observer):
def update(self, temperature: float) -> None:
print(f"⌚ Smart Watch: Temperature is {temperature}°C")
smart_watch = SmartWatch()
weather_station.attach(smart_watch)
weather_station.set_temperature(22.0)
if __name__ == "__main__":
main()
Diagram

Here’s how the Observer Pattern works in practice - showing the sequence of notifications:

sequenceDiagram
    participant Subject as WeatherStation
    participant Observer1 as PhoneApp
    participant Observer2 as Website
    participant Observer3 as Billboard
    
    Observer1->>Subject: attach(observer1)
    Observer2->>Subject: attach(observer2)
    Observer3->>Subject: attach(observer3)
    
    Note over Subject: Observers registered
    
    Subject->>Subject: set_temperature(25.0)
    Subject->>Subject: notify()
    
    Subject->>Observer1: update(25.0)
    activate Observer1
    Observer1-->>Subject: Display updated
    deactivate Observer1
    
    Subject->>Observer2: update(25.0)
    activate Observer2
    Observer2-->>Subject: Display updated
    deactivate Observer2
    
    Subject->>Observer3: update(25.0)
    activate Observer3
    Observer3-->>Subject: Display updated
    deactivate Observer3
    
    Note over Subject,Observer3: All observers notified<br/>automatically!

Real-World Software Example: E-Commerce Order System

Section titled “Real-World Software Example: E-Commerce Order System”

Now let’s see a realistic software example - an e-commerce system where multiple services need to be notified when an order is placed.

You’re building an e-commerce system. When an order is placed, you need to:

  • Send confirmation email
  • Update inventory
  • Process payment
  • Send notification to warehouse
  • Update analytics

Without Observer Pattern:

bad_order.py
# ❌ Without Observer Pattern
class Order:
def __init__(self, order_id: str, items: list, total: float):
self.order_id = order_id
self.items = items
self.total = total
self.status = "pending"
# Problem: Order knows about all services!
self.email_service = None
self.inventory_service = None
self.payment_service = None
self.warehouse_service = None
self.analytics_service = None
def place_order(self):
"""Place order and notify all services"""
self.status = "placed"
# Problem: Manual notification to each service
if self.email_service:
self.email_service.send_confirmation(self.order_id)
if self.inventory_service:
self.inventory_service.update_inventory(self.items)
if self.payment_service:
self.payment_service.process_payment(self.order_id, self.total)
if self.warehouse_service:
self.warehouse_service.notify_warehouse(self.order_id, self.items)
if self.analytics_service:
self.analytics_service.record_order(self.order_id, self.total)
# Problems:
# - Order class knows about all services
# - Need to modify place_order() to add new services
# - Hard to test - need to mock all services
# - Violates Single Responsibility Principle
class EmailService:
def send_confirmation(self, order_id: str):
print(f"📧 Email sent: Order {order_id} confirmation")
class InventoryService:
def update_inventory(self, items: list):
print(f"📦 Inventory updated for {len(items)} items")
class PaymentService:
def process_payment(self, order_id: str, amount: float):
print(f"💳 Payment processed: ${amount} for order {order_id}")
class WarehouseService:
def notify_warehouse(self, order_id: str, items: list):
print(f"🏭 Warehouse notified: Order {order_id} with {len(items)} items")
class AnalyticsService:
def record_order(self, order_id: str, amount: float):
print(f"📊 Analytics: Order {order_id} recorded, amount: ${amount}")
# Usage
order = Order("ORD-123", ["item1", "item2"], 99.99)
order.email_service = EmailService()
order.inventory_service = InventoryService()
order.payment_service = PaymentService()
order.warehouse_service = WarehouseService()
order.analytics_service = AnalyticsService()
order.place_order()

Problems:

  • Order class knows about all services (tight coupling)
  • Need to modify place_order() to add new services
  • Hard to test - need to mock all services
  • Violates Single Responsibility Principle
  • Can’t easily add/remove services at runtime
classDiagram
    class OrderSubject {
        <<abstract>>
        +attach(observer) void
        +detach(observer) void
        +notify() void
    }
    class OrderObserver {
        <<abstract>>
        +update(order) void
    }
    class Order {
        -orderId: str
        -items: List
        -amount: float
        -observers: List~OrderObserver~
        +attach(observer) void
        +detach(observer) void
        +notify() void
        +place_order() void
    }
    class EmailService {
        +update(order) void
        +send_confirmation(order) void
    }
    class InventoryService {
        +update(order) void
        +update_stock(order) void
    }
    class PaymentService {
        +update(order) void
        +process_payment(order) void
    }
    class WarehouseService {
        +update(order) void
        +prepare_shipment(order) void
    }
    class AnalyticsService {
        +update(order) void
        +record_order(order) void
    }
    
    OrderSubject <|-- Order : implements
    OrderObserver <|-- EmailService : implements
    OrderObserver <|-- InventoryService : implements
    OrderObserver <|-- PaymentService : implements
    OrderObserver <|-- WarehouseService : implements
    OrderObserver <|-- AnalyticsService : implements
    Order --> OrderObserver : notifies
    
    note for Order "When order is placed,<br/>all services notified automatically"
    note for OrderObserver "Each service handles<br/>its own responsibility"
order_observer.py
from abc import ABC, abstractmethod
from typing import List, Dict, Any
from dataclasses import dataclass
# Step 1: Define the Observer interface
class OrderObserver(ABC):
"""Interface for order observers"""
@abstractmethod
def on_order_placed(self, order_data: Dict[str, Any]) -> None:
"""Called when an order is placed"""
pass
# Step 2: Define the Subject interface
class OrderSubject(ABC):
"""Interface for order subjects"""
@abstractmethod
def attach(self, observer: OrderObserver) -> None:
"""Attach an observer"""
pass
@abstractmethod
def detach(self, observer: OrderObserver) -> None:
"""Detach an observer"""
pass
@abstractmethod
def notify_order_placed(self, order_data: Dict[str, Any]) -> None:
"""Notify all observers about order placement"""
pass
# Step 3: Create concrete Subject
@dataclass
class Order:
"""Order data class"""
order_id: str
items: List[str]
total: float
status: str = "pending"
class OrderService(OrderSubject):
"""Order service - the subject being observed"""
def __init__(self):
self._observers: List[OrderObserver] = []
def attach(self, observer: OrderObserver) -> None:
"""Subscribe an observer"""
if observer not in self._observers:
self._observers.append(observer)
print(f"✅ Service subscribed: {observer.__class__.__name__}")
def detach(self, observer: OrderObserver) -> None:
"""Unsubscribe an observer"""
if observer in self._observers:
self._observers.remove(observer)
print(f"❌ Service unsubscribed: {observer.__class__.__name__}")
def notify_order_placed(self, order_data: Dict[str, Any]) -> None:
"""Notify all observers about order placement"""
for observer in self._observers:
observer.on_order_placed(order_data)
def place_order(self, order: Order) -> None:
"""Place an order and notify all observers"""
order.status = "placed"
print(f"\n🛒 Order {order.order_id} placed!")
order_data = {
"order_id": order.order_id,
"items": order.items,
"total": order.total,
"status": order.status
}
# Notify all observers automatically!
self.notify_order_placed(order_data)
# Step 4: Create concrete Observers
class EmailService(OrderObserver):
"""Email service observer"""
def on_order_placed(self, order_data: Dict[str, Any]) -> None:
order_id = order_data["order_id"]
print(f"📧 Email sent: Order {order_id} confirmation")
class InventoryService(OrderObserver):
"""Inventory service observer"""
def on_order_placed(self, order_data: Dict[str, Any]) -> None:
items = order_data["items"]
print(f"📦 Inventory updated for {len(items)} items")
class PaymentService(OrderObserver):
"""Payment service observer"""
def on_order_placed(self, order_data: Dict[str, Any]) -> None:
order_id = order_data["order_id"]
amount = order_data["total"]
print(f"💳 Payment processed: ${amount} for order {order_id}")
class WarehouseService(OrderObserver):
"""Warehouse service observer"""
def on_order_placed(self, order_data: Dict[str, Any]) -> None:
order_id = order_data["order_id"]
items = order_data["items"]
print(f"🏭 Warehouse notified: Order {order_id} with {len(items)} items")
class AnalyticsService(OrderObserver):
"""Analytics service observer"""
def on_order_placed(self, order_data: Dict[str, Any]) -> None:
order_id = order_data["order_id"]
amount = order_data["total"]
print(f"📊 Analytics: Order {order_id} recorded, amount: ${amount}")
# Step 5: Use the pattern
def main():
# Create order service
order_service = OrderService()
# Create services (observers)
email_service = EmailService()
inventory_service = InventoryService()
payment_service = PaymentService()
warehouse_service = WarehouseService()
analytics_service = AnalyticsService()
# Subscribe all services
order_service.attach(email_service)
order_service.attach(inventory_service)
order_service.attach(payment_service)
order_service.attach(warehouse_service)
order_service.attach(analytics_service)
# Place an order - all services get notified automatically!
order = Order("ORD-123", ["Laptop", "Mouse"], 999.99)
order_service.place_order(order)
# Add a new service - no need to modify OrderService!
class NotificationService(OrderObserver):
def on_order_placed(self, order_data: Dict[str, Any]) -> None:
order_id = order_data["order_id"]
print(f"🔔 Push notification sent: Order {order_id} placed")
notification_service = NotificationService()
order_service.attach(notification_service)
# Place another order - new service also gets notified!
order2 = Order("ORD-124", ["Keyboard"], 49.99)
order_service.place_order(order2)
if __name__ == "__main__":
main()

With Observer Pattern, adding a new service is super easy:

adding_new_service.py
# Step 1: Create the new service observer
class LoyaltyPointsService(OrderObserver):
"""Loyalty points service observer"""
def on_order_placed(self, order_data: Dict[str, Any]) -> None:
order_id = order_data["order_id"]
amount = order_data["total"]
points = int(amount * 0.1) # 10% of order value
print(f"🎁 Loyalty points added: {points} points for order {order_id}")
# Step 2: Subscribe it (that's it!)
loyalty_service = LoyaltyPointsService()
order_service.attach(loyalty_service)
# Now it automatically gets notified for all future orders!
# No changes needed to OrderService or place_order() method!

There are several variations of the Observer Pattern:

Subject sends all data to observers:

push_model.py
class Observer(ABC):
@abstractmethod
def update(self, data: Dict[str, Any]) -> None:
pass
# Subject pushes all data
def notify(self):
for observer in self._observers:
observer.update({
"temperature": self._temperature,
"humidity": self._humidity,
"pressure": self._pressure
})

Pros: Observers get all data
Cons: Observers might not need all data

Observers pull data they need from subject:

pull_model.py
class Observer(ABC):
@abstractmethod
def update(self, subject: Subject) -> None:
# Observer pulls what it needs
temp = subject.get_temperature()
humidity = subject.get_humidity()
pass
# Subject just notifies, observers pull what they need
def notify(self):
for observer in self._observers:
observer.update(self) # Pass subject reference

Pros: Observers get only what they need
Cons: Observers need to know subject’s interface

Using events instead of direct method calls:

event_based.py
from typing import Callable
class EventObserver:
"""Event-based observer using callbacks"""
def __init__(self):
self._callbacks: List[Callable] = []
def subscribe(self, callback: Callable) -> None:
self._callbacks.append(callback)
def notify(self, event_data: Dict[str, Any]) -> None:
for callback in self._callbacks:
callback(event_data)
# Usage
def on_order_placed(data):
print(f"Order {data['order_id']} placed!")
observer = EventObserver()
observer.subscribe(on_order_placed)
observer.notify({"order_id": "ORD-123"})

Use Observer Pattern when:

One object changes and multiple objects need to be notified
You want loose coupling - Subject shouldn’t know about observers
You need dynamic relationships - Add/remove observers at runtime
You want to avoid polling - No constant checking for changes
You have one-to-many dependency - One subject, many observers
You’re following Open/Closed Principle - Open for extension, closed for modification

Don’t use Observer Pattern when:

Simple one-to-one communication - Direct method call is simpler
Performance is critical - Observer adds overhead (usually negligible)
Order of notifications matters - Observers might execute in unpredictable order
Observers need to modify subject - Can create circular dependencies
You have few, stable observers - Overhead might not be worth it


circular_dependency.py
# ❌ Observer modifying subject - can cause infinite loops!
class BadObserver(Observer):
def update(self, temperature: float):
if temperature > 30:
self.subject.set_temperature(25) # Modifying subject!
# This triggers notify() again, which calls update() again... infinite loop!
# ✅ Better: Observer should only react, not modify
class GoodObserver(Observer):
def update(self, temperature: float):
if temperature > 30:
self.send_alert() # Just react, don't modify subject
error_handling.py
# ❌ If one observer fails, others don't get notified
def notify(self):
for observer in self._observers:
observer.update(self._temperature) # If this fails, loop stops!
# ✅ Better: Handle errors so all observers get notified
def notify(self):
for observer in self._observers:
try:
observer.update(self._temperature)
except Exception as e:
print(f"Error notifying {observer}: {e}")
# Continue with other observers

Mistake 3: Memory Leaks (Not Unsubscribing)

Section titled “Mistake 3: Memory Leaks (Not Unsubscribing)”
memory_leak.py
# ❌ Observer holds reference to subject, subject holds reference to observer
# If observer is deleted but not unsubscribed, memory leak!
class BadObserver(Observer):
def __init__(self, subject: Subject):
self.subject = subject
subject.attach(self) # Attached but never detached
# ✅ Better: Always unsubscribe when done
class GoodObserver(Observer):
def __init__(self, subject: Subject):
self.subject = subject
subject.attach(self)
def cleanup(self):
self.subject.detach(self) # Always clean up!

  1. Loose Coupling - Subject doesn’t depend on concrete observer classes
  2. Dynamic Relationships - Observers can be added/removed at runtime
  3. Open/Closed Principle - Easy to add observers without modifying subject
  4. Broadcast Communication - One notification reaches all observers
  5. No Polling - Observers don’t need to constantly check for changes
  6. Follows SOLID Principles - Especially Open/Closed and Dependency Inversion

Observer Pattern is a behavioral design pattern that defines a one-to-many dependency between objects. When one object (subject) changes state, all dependent objects (observers) are notified and updated automatically.

  • Loose coupling - Subject doesn’t know about specific observers
  • Dynamic subscription - Add/remove observers at runtime
  • Broadcast communication - One change notifies all observers
  • Avoid polling - No need to constantly check for changes
  • Follow Open/Closed Principle
  1. Define Observer interface - What all observers can do
  2. Define Subject interface - Methods to attach/detach/notify
  3. Create concrete Subject - Maintains list of observers
  4. Create concrete Observers - React to subject changes
  5. Subscribe observers - Attach observers to subject
  6. Notify on change - Subject notifies all observers when state changes
Subject → attach/detach → Observers
Subject → notify() → Observer.update()
  • Subject - The object being observed (has state)
  • Observer - Objects that watch the subject
  • attach() - Subscribe an observer
  • detach() - Unsubscribe an observer
  • notify() - Notify all observers
  • update() - Observer’s reaction method
# Observer interface
class Observer(ABC):
@abstractmethod
def update(self, data): pass
# Subject
class Subject:
def __init__(self):
self._observers = []
self._state = None
def attach(self, observer):
self._observers.append(observer)
def notify(self):
for observer in self._observers:
observer.update(self._state)
# Concrete Observer
class ConcreteObserver(Observer):
def update(self, data):
print(f"Updated: {data}")
# Usage
subject = Subject()
observer = ConcreteObserver()
subject.attach(observer)
subject.notify()

✅ One-to-many dependency
✅ Subject changes and multiple objects need notification
✅ Want loose coupling
✅ Need dynamic add/remove observers
✅ Want to avoid polling

❌ Simple one-to-one communication
❌ Performance is critical
❌ Order of notifications matters
❌ Few, stable observers

  • Observer Pattern = One subject, many observers
  • Subject = Object being watched
  • Observer = Objects watching the subject
  • Benefit = Loose coupling, dynamic relationships
  • Principle = Open for extension, closed for modification
# 1. Observer Interface
class Observer(ABC):
@abstractmethod
def update(self, data): pass
# 2. Subject Interface
class Subject(ABC):
@abstractmethod
def attach(self, observer): pass
@abstractmethod
def detach(self, observer): pass
@abstractmethod
def notify(self): pass
# 3. Concrete Subject
class ConcreteSubject(Subject):
def __init__(self):
self._observers = []
self._state = None
def attach(self, observer):
self._observers.append(observer)
def notify(self):
for observer in self._observers:
observer.update(self._state)
# 4. Concrete Observer
class ConcreteObserver(Observer):
def update(self, data):
# React to change
pass
# 5. Usage
subject = ConcreteSubject()
observer = ConcreteObserver()
subject.attach(observer)
subject.notify()
  • Observer Pattern decouples subject from observers
  • It enables dynamic relationships - add/remove at runtime
  • It follows Open/Closed Principle - easy to extend
  • Use it when you need one-to-many notification
  • Don’t use it for simple one-to-one cases - avoid over-engineering!

What to say:

“Observer Pattern is a behavioral design pattern that defines a one-to-many dependency between objects. When the subject (the object being observed) changes state, all its observers are automatically notified and updated.”

Why it matters:

  • Shows you understand the fundamental purpose
  • Demonstrates knowledge of behavioral patterns category
  • Indicates you can explain concepts clearly

Must mention:

  • One-to-many dependency - One subject, many observers
  • Loose coupling - Subject shouldn’t know about specific observers
  • Dynamic relationships - Add/remove observers at runtime
  • Avoid polling - No need to constantly check for changes
  • Event-driven architecture - React to events automatically

Example scenario to give:

“I’d use Observer Pattern when building a stock price monitoring system. When a stock price changes, multiple components need to react - the UI needs to update, alerts need to be sent, analytics need to be recorded. With Observer Pattern, the stock service just notifies all observers, and each handles the update differently.”

Must explain:

  1. Subject (Observable) - The object being observed, maintains list of observers
  2. Observer - Interface for objects that watch the subject
  3. Concrete Subject - Specific subject implementation
  4. Concrete Observer - Specific observer implementations
  5. attach() - Subscribe an observer
  6. detach() - Unsubscribe an observer
  7. notify() - Notify all observers
  8. update() - Observer’s reaction method

Visual explanation:

Subject → attach(observer) → Observer list
Subject → notify() → Observer.update()

Benefits to mention:

  • Loose Coupling - Subject doesn’t depend on concrete observer classes
  • Dynamic Relationships - Observers can be added/removed at runtime
  • Open/Closed Principle - Easy to add observers without modifying subject
  • Broadcast Communication - One notification reaches all observers
  • No Polling - Observers don’t need to constantly check

Trade-offs to acknowledge:

  • Performance - Notifying many observers can be slow
  • Order of execution - Observers might execute in unpredictable order
  • Memory leaks - Need to properly unsubscribe observers
  • Debugging difficulty - Hard to trace notification flow

Q: “What’s the difference between Observer Pattern and Pub-Sub Pattern?”

A:

“Observer Pattern has direct communication - the subject knows about observers and calls their update methods directly. Pub-Sub (Publish-Subscribe) Pattern uses a message broker - publishers and subscribers don’t know about each other, they communicate through a broker. Pub-Sub is more decoupled but adds complexity.”

Q: “How do you handle errors in Observer Pattern?”

A:

“I wrap each observer notification in a try-except block. If one observer fails, I log the error and continue notifying other observers. This ensures that one failing observer doesn’t prevent others from being notified. I might also implement a retry mechanism or dead-letter queue for critical observers.”

Q: “How does Observer Pattern relate to SOLID principles?”

A:

“Observer Pattern primarily supports the Open/Closed Principle - you can add new observers without modifying the subject. It also supports Dependency Inversion Principle by making subjects depend on the Observer abstraction rather than concrete observer classes. Additionally, it helps with Single Responsibility Principle by separating the subject’s core logic from notification logic.”

Key implementation points:

  1. Use Abstract Base Class (ABC) for observer interface

    from abc import ABC, abstractmethod
    class Observer(ABC):
    @abstractmethod
    def update(self, data): pass
  2. Maintain observer list - Use a list or set to store observers

    def __init__(self):
    self._observers: List[Observer] = []
  3. Handle duplicate subscriptions - Check if observer already exists

    def attach(self, observer: Observer):
    if observer not in self._observers:
    self._observers.append(observer)
  4. Error handling - Wrap notifications in try-except

    def notify(self):
    for observer in self._observers:
    try:
    observer.update(self._state)
    except Exception as e:
    logger.error(f"Error notifying {observer}: {e}")

Good examples to mention:

  • Model-View-Controller (MVC) - Model notifies views when data changes
  • Event-driven systems - UI events, button clicks, form submissions
  • Stock market - Stock prices notify multiple displays
  • Logging systems - Log events notify multiple handlers
  • Notification systems - Order placed notifies email, SMS, push services
  • GUI frameworks - Widgets notify listeners on events

Mistakes interviewers watch for:

  1. Memory leaks - Not unsubscribing observers

    • ❌ Bad: Attach observer, never detach
    • ✅ Good: Always detach when observer is no longer needed
  2. Observers modifying subject - Can cause infinite loops

    • ❌ Bad: Observer calls subject.set_state() in update()
    • ✅ Good: Observer only reacts, doesn’t modify subject
  3. No error handling - One failing observer stops all notifications

    • ❌ Bad: No try-except in notify()
    • ✅ Good: Wrap each notification in try-except
  4. Order dependencies - Assuming observers execute in specific order

    • ❌ Bad: Observer A depends on Observer B executing first
    • ✅ Good: Observers are independent

Observer vs Strategy:

  • Observer - One subject notifies many observers about changes
  • Strategy - One context uses one strategy at a time (algorithm selection)

Observer vs Mediator:

  • Observer - Direct communication, subject knows about observers
  • Mediator - Centralized communication through mediator

Observer vs Chain of Responsibility:

  • Observer - All observers get notified
  • Chain of Responsibility - Request passes through chain until handled

What interviewers look for:

Clean code - Readable, well-structured
Type hints - Proper type annotations
Error handling - Handle observer failures gracefully
Memory management - Proper cleanup, avoid leaks
SOLID principles - Follows design principles
Testability - Easy to test and mock

Example of good code:

from abc import ABC, abstractmethod
from typing import List, Dict, Any
import logging
logger = logging.getLogger(__name__)
class Observer(ABC):
"""Interface for observers"""
@abstractmethod
def update(self, data: Dict[str, Any]) -> None:
"""Called when subject's state changes"""
pass
class Subject(ABC):
"""Interface for subjects"""
@abstractmethod
def attach(self, observer: Observer) -> None:
"""Attach an observer"""
pass
@abstractmethod
def detach(self, observer: Observer) -> None:
"""Detach an observer"""
pass
@abstractmethod
def notify(self) -> None:
"""Notify all observers"""
pass
class WeatherStation(Subject):
"""Weather station subject"""
def __init__(self):
self._temperature = 0
self._observers: List[Observer] = []
def attach(self, observer: Observer) -> None:
"""Subscribe an observer"""
if observer not in self._observers:
self._observers.append(observer)
logger.info(f"Observer attached: {observer.__class__.__name__}")
def detach(self, observer: Observer) -> None:
"""Unsubscribe an observer"""
if observer in self._observers:
self._observers.remove(observer)
logger.info(f"Observer detached: {observer.__class__.__name__}")
def notify(self) -> None:
"""Notify all observers with error handling"""
data = {"temperature": self._temperature}
for observer in self._observers:
try:
observer.update(data)
except Exception as e:
logger.error(f"Error notifying {observer}: {e}")
# Continue with other observers
def set_temperature(self, temperature: float) -> None:
"""Set temperature and notify observers"""
self._temperature = temperature
self.notify()

Before your interview, make sure you can:

  • Define Observer Pattern clearly in one sentence
  • Explain when to use it (with examples)
  • Describe the structure and components
  • List benefits and trade-offs
  • Compare with other behavioral patterns
  • Implement Observer Pattern from scratch
  • Handle errors and memory leaks
  • Connect to SOLID principles
  • Identify when NOT to use it
  • Give 2-3 real-world examples
  • Discuss common mistakes and how to avoid them

Remember: Observer Pattern is about keeping objects informed automatically - when one object changes, all observers get notified without tight coupling! 👀