How to Implement a Simple Event Bus in Python

Create a publish-subscribe event bus using dataclasses and defaultdict to decouple event producers from consumers.

Easy Python 3.9+ Aug 9, 2026 System design patterns 15 views 0 copies

Python code

38 lines
Python 3.9+
from collections import defaultdict
from dataclasses import dataclass, field
from typing import Callable, Dict, List, Set


@dataclass
class EventBus:
    _subscribers: Dict[str, List[Callable]] = field(
        default_factory=lambda: defaultdict(list)
    )

    def subscribe(self, event_type: str, handler: Callable) -> None:
        self._subscribers[event_type].append(handler)

    def publish(self, event_type: str, payload: dict = None) -> None:
        for handler in self._subscribers.get(event_type, []):
            handler(payload or {})


def order_created_handler(data):
    print(f"Order created: {data['order_id']} for {data['customer']}")


def inventory_handler(data):
    print(f"Inventory reserved: {data['quantity']} units of {data['sku']}")


if __name__ == "__main__":
    bus = EventBus()
    bus.subscribe("order.created", order_created_handler)
    bus.subscribe("order.created", inventory_handler)

    bus.publish("order.created", {
        "order_id": 12345,
        "customer": "Alice",
        "sku": "BOOK-001",
        "quantity": 2
    })

Output

stdout
Order created: 12345 for Alice
Inventory reserved: 2 units of BOOK-001

How it works

The EventBus dataclass uses a defaultdict(list) to automatically create a new list for each event type on first access. The subscribe method appends handler callables to the list for a given event type. The publish method retrieves the handlers for the event type (defaulting to an empty list) and calls each with the payload. Handlers are called synchronously in the order they were subscribed, making this a simple in-process implementation without external dependencies.

Common mistakes

  • Forgetting that handlers run synchronously, which can block the publisher.
  • Not using a default dict, leading to KeyError when publishing to an unsubscribed event.
  • Assuming handlers receive the same payload object without copying, causing shared-state bugs.
  • Using `payload or {}` which drops valid empty dictionaries or falsy values like 0.

Variations

  1. Use `asyncio` to make the bus async and publish events without blocking the main thread.
  2. Add priority ordering by storing handlers in a sorted list or using a `SortedDict`.

Real-world use cases

  • E-commerce platforms decoupling order placement from email notifications and inventory updates.
  • Microservices triggering side effects like logging or analytics when user signup happens.
  • Plugin systems allowing third-party modules to react to core application events without tight coupling.

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from System design patterns

Related tutorials and quizzes for this topic.