How to Implement the Observer Pattern in Python

by SkillAiNest

Have you ever wondered how YouTube notifies you when your favorite channel uploads a new video? Or how does your email client alert you when new messages arrive? These are excellent examples of the observer pattern in action.

The observer pattern is a design pattern where an object (called a subject) maintains a list of dependents (called observers) and automatically notifies them when its state changes. It’s like a newsletter subscription: when new content is published, all subscribers are notified.

In this tutorial, you’ll learn what the observer pattern is, why it’s useful, and how to implement it in Python with practical examples.

You can find the code. on GitHub.

Conditions

Before we begin, make sure you have:

  • Python 3.10 or higher is installed.

  • Understanding how Python classes and methods work

  • Familiarity with Object Oriented Programming (OOP) concepts

Let’s begin!

Table of Contents

What is Observer Pattern?

The observer defines pattern a. One-to-many relationship between objects. When an object changes state, all its dependents are automatically notified and updated..

Think of it like a news agency and reporters. When breaking news occurs (topic), the agency immediately notifies all subscribed reporters (observers). Each reporter can then handle the news in their own way – some may tweet it, others may write articles, and some may broadcast it on TV.

The pattern is useful when:

  • You need to notify multiple objects about state changes.

  • You want loose coupling between objects.

  • You don’t know how many items need to be notified in advance.

  • The object should be able to subscribe and unsubscribe dynamically.

An example of a simple observer pattern

Let’s start with a basic example: a blog that notifies readers when a new article is published.

We will create a blog (topic) and email subscribers (commentators) who are automatically notified when new content is published.

First, let’s build the Blog Class that will manage subscribers and send notifications:

class Blog:
    def __init__(self, name):
        self.name = name
        self._subscribers = ()
        self._latest_post = None

    def subscribe(self, subscriber):
        """Add a subscriber to the blog"""
        if subscriber not in self._subscribers:
            self._subscribers.append(subscriber)
            print(f"✓ {subscriber.email} subscribed to {self.name}")

    def unsubscribe(self, subscriber):
        """Remove a subscriber from the blog"""
        if subscriber in self._subscribers:
            self._subscribers.remove(subscriber)
            print(f"✗ {subscriber.email} unsubscribed from {self.name}")

    def notify_all(self):
        """Send notifications to all subscribers"""
        print(f"\nNotifying {len(self._subscribers)} subscribers...")
        for subscriber in self._subscribers:
            subscriber.receive_notification(self.name, self._latest_post)

    def publish_post(self, title):
        """Publish a new post and notify subscribers"""
        print(f"\n📝 {self.name} published: '{title}'")
        self._latest_post = title
        self.notify_all()

gave Blog Class is our subject. It maintains a list of subscribers. _subscribers and stores the latest post title in it. _latest_post. The subscribe method adds subscribers to the list while checking for duplicates. The notify_all method loops through all subscribers and calls their receive_notify method. When we call publish_post it updates the latest post and automatically notifies all subscribers.

Now let’s create the Observer class that receives the notifications:

class EmailSubscriber:
    def __init__(self, email):
        self.email = email

    def receive_notification(self, blog_name, post_title):
        print(f"📧 Email sent to {self.email}: New post on {blog_name} - '{post_title}'")

gave EmailSubscriber Class is our observer. There is a way, receive_notificationwhich handles notifications from the blog.

Let’s now use these classes together:


tech_blog = Blog("DevDaily")


reader1 = EmailSubscriber("anna@example.com")
reader2 = EmailSubscriber("betty@example.com")
reader3 = EmailSubscriber("cathy@example.com")


tech_blog.subscribe(reader1)
tech_blog.subscribe(reader2)
tech_blog.subscribe(reader3)


tech_blog.publish_post("10 Python Tips for Beginners")
tech_blog.publish_post("Understanding Design Patterns")

Output:

✓ anna@example.com subscribed to DevDaily
✓ betty@example.com subscribed to DevDaily
✓ cathy@example.com subscribed to DevDaily

📝 DevDaily published: '10 Python Tips for Beginners'

Notifying 3 subscribers...
📧 Email sent to anna@example.com: New post on DevDaily - '10 Python Tips for Beginners'
📧 Email sent to betty@example.com: New post on DevDaily - '10 Python Tips for Beginners'
📧 Email sent to cathy@example.com: New post on DevDaily - '10 Python Tips for Beginners'

📝 DevDaily published: 'Understanding Design Patterns'

Notifying 3 subscribers...
📧 Email sent to anna@example.com: New post on DevDaily - 'Understanding Design Patterns'
📧 Email sent to betty@example.com: New post on DevDaily - 'Understanding Design Patterns'
📧 Email sent to cathy@example.com: New post on DevDaily - 'Understanding Design Patterns'

Note how Blog The class does not need to know the details of how each subscriber handles the notification. It just calls them. receive_notification method

Note: Think of all the examples here as placeholder functions that explain how the observer pattern works. In your projects, you will have functions that connect to email and other services.

Handling unsubscribes

In real applications, users need to be able to unsubscribe. Here’s how it works:

blog = Blog("CodeMaster")

user1 = EmailSubscriber("john@example.com")
user2 = EmailSubscriber("jane@example.com")


blog.subscribe(user1)
blog.subscribe(user2)


blog.publish_post("Getting Started with Python")


blog.unsubscribe(user1)


blog.publish_post("Advanced Python Techniques")

Output:

✓ john@example.com subscribed to CodeMaster
✓ jane@example.com subscribed to CodeMaster

📝 CodeMaster published: 'Getting Started with Python'

Notifying 2 subscribers...
📧 Email sent to john@example.com: New post on CodeMaster - 'Getting Started with Python'
📧 Email sent to jane@example.com: New post on CodeMaster - 'Getting Started with Python'
✗ john@example.com unsubscribed from CodeMaster

📝 CodeMaster published: 'Advanced Python Techniques'

Notifying 1 subscribers...
📧 Email sent to jane@example.com: New post on CodeMaster - 'Advanced Python Techniques'

After user1 Unsubscribes, only user2 Receive a notification for another post. The observer pattern makes it easy to dynamically add and remove observers.

Different types of observers

One of the most useful aspects of the observer pattern is that different observers may react differently to the same event. Let’s create a stock price tracker where multiple observer types respond to price changes.

First, let’s create. Stock The class that will notify observers of a price change:

class Stock:
    def __init__(self, symbol, price):
        self.symbol = symbol
        self._price = price
        self._observers = ()

    def add_observer(self, observer):
        self._observers.append(observer)
        print(f"Observer added: {observer.__class__.__name__}")

    def remove_observer(self, observer):
        self._observers.remove(observer)

    def notify_observers(self):
        for observer in self._observers:
            observer.update(self.symbol, self._price)

    def set_price(self, price):
        print(f"\n {self.symbol} price changed: ${self._price} → ${price}")
        self._price = price
        self.notify_observers()

gave Stock The class maintains the current value and notifies all observers whenever. set_price is called.

Now let’s create three different observer types that respond differently to price updates:

class EmailAlert:
    def __init__(self, email):
        self.email = email

    def update(self, symbol, price):
        print(f"📧 Sending email to {self.email}: {symbol} is now ${price}")

class SMSAlert:
    def __init__(self, phone):
        self.phone = phone

    def update(self, symbol, price):
        print(f"📱 Sending SMS to {self.phone}: {symbol} price update ${price}")

class Logger:
    def update(self, symbol, price):
        print(f"📝 Logging: {symbol} = ${price} at system time")

Each observer has a different implementation of the update method. EmailAlert sends email, SMSAlert Sends text messages, and Logger Records the change.

Now let’s use them together:


apple_stock = Stock("AAPL", 150.00)


email_notifier = EmailAlert("investor@example.com")
sms_notifier = SMSAlert("+1234567890")
price_logger = Logger()


apple_stock.add_observer(email_notifier)
apple_stock.add_observer(sms_notifier)
apple_stock.add_observer(price_logger)


apple_stock.set_price(155.50)
apple_stock.set_price(152.25)

Output:

Observer added: EmailAlert
Observer added: SMSAlert
Observer added: Logger

 AAPL price changed: $150.0 → $155.5
📧 Sending email to investor@example.com: AAPL is now $155.5
📱 Sending SMS to +1234567890: AAPL price update $155.5
📝 Logging: AAPL = $155.5 at system time

 AAPL price changed: $155.5 → $152.25
📧 Sending email to investor@example.com: AAPL is now $152.25
📱 Sending SMS to +1234567890: AAPL price update $152.25
📝 Logging: AAPL = $152.25 at system time

gave Stock The class doesn’t care what each observer does. It just calls. update Each on and passes the necessary data. You can mix and match observers however you like.

Using abstract base classes

To implement a consistent interface across all observers, we can use Python’s Abstract base classes. This guarantees type safety.

First, let’s create the base classes that define our interface:

from abc import ABC, abstractmethod

class Subject(ABC):
    def __init__(self):
        self._observers = ()

    def attach(self, observer):
        if observer not in self._observers:
            self._observers.append(observer)

    def detach(self, observer):
        self._observers.remove(observer)

    def notify(self, data):
        for observer in self._observers:
            observer.update(data)

class Observer(ABC):
    @abstractmethod
    def update(self, data):
        pass

gave Subject The class provides standard observer management methods. gave Observer Defines an interface with a class. @abstractmethod The decorator ensures all observers implement the update.

Now let’s create an order system that uses these base classes:

class OrderSystem(Subject):
    def __init__(self):
        super().__init__()
        self._order_id = None

    def place_order(self, order_id, items):
        print(f"\n🛒 Order #{order_id} placed with {len(items)} items")
        self._order_id = order_id
        self.notify({"order_id": order_id, "items": items})

gave OrderSystem Inherited from Subject and can manage observers without implementing this logic itself.

Next, let’s create concrete observers for different departments:

class InventoryObserver(Observer):
    def update(self, data):
        print(f"📦 Inventory: Updating stock for order #{data('order_id')}")

class ShippingObserver(Observer):
    def update(self, data):
        print(f"🚚 Shipping: Preparing shipment for order #{data('order_id')}")

class BillingObserver(Observer):
    def update(self, data):
        print(f"💳 Billing: Processing payment for order #{data('order_id')}")

Each observer It is necessary Apply update Method Now we put it all together:


order_system = OrderSystem()


inventory = InventoryObserver()
shipping = ShippingObserver()
billing = BillingObserver()


order_system.attach(inventory)
order_system.attach(shipping)
order_system.attach(billing)


order_system.place_order("ORD-12345", ("Laptop", "Mouse", "Keyboard"))

Output:

🛒 Order #ORD-12345 placed with 3 items
📦 Inventory: Updating stock for order #ORD-12345
🚚 Shipping: Preparing shipment for order #ORD-12345
💳 Billing: Processing payment for order #ORD-12345

Using abstract base classes provides type safety and ensures that all observers follow the same interface.

When to Use the Observer Pattern

The observer pattern is suitable for:

  • Event-driven systems – GUI frameworks, game engines, or any system where actions trigger updates elsewhere.

  • Real-time notifications – chat apps, social media feeds, stock tickers, or push notification systems.

  • Decoupled architecture – when you want the subject to be independent of its observers for flexibility.

  • Multiple listeners – When multiple objects need to react differently to the same event.

Avoid the Observer pattern when you have simple one-to-one relationships, or when performance is important with many observers (because the notification overhead can be significant).

The result

The observer pattern creates a clean separation between objects that generate events and objects that respond to them. This promotes loose coupling – the subject doesn’t need to know anything about its observers except that they have an update method.

We’ve covered basic implementations, handling subscriptions, using different observer types, and abstract base classes. Start simple with basic subject and observer relationships and add complexity only when needed.

Happy coding!

You may also like

Leave a Comment

At Skillainest, we believe the future belongs to those who embrace AI, upgrade their skills, and stay ahead of the curve.

Get latest news

Subscribe my Newsletter for new blog posts, tips & new photos. Let's stay updated!

@2025 Skillainest.Designed and Developed by Pro