← System Design
📣
System Design

Observer · Pub/Sub

Publishers fire events without knowing who listens. The pattern behind Kafka, EventBridge, and every reactive UI.

TL;DR

Observer (a.k.a. Publish/Subscribe) decouples *who fires an event* from *who reacts to it*. The publisher emits an event; zero or more subscribers receive it. They don't know about each other; the publisher doesn't know how many there are. This is the pattern behind every event bus, message broker, reactive UI, and webhook system. Add a new subscriber and the publisher doesn't change one line.

When to use

Any time you have a one-to-many fan-out where the producer shouldn't know about consumers — UI events, domain events, webhooks, log streams, change-data-capture pipelines, multi-team notification systems. The pattern scales from in-process EventEmitters to Kafka clusters spanning continents.

See it

Simulate it

The pattern in one line

The pattern in code

type Listener<T> = (event: T) => void;

class EventEmitter<T> {
  private listeners: Listener<T>[] = [];
  subscribe(fn: Listener<T>) { this.listeners.push(fn); return () => this.unsubscribe(fn); }
  unsubscribe(fn: Listener<T>) { this.listeners = this.listeners.filter((l) => l !== fn); }
  emit(event: T) { for (const l of this.listeners) l(event); }
}

const orderEvents = new EventEmitter<OrderPlaced>();
orderEvents.subscribe((e) => sendConfirmationEmail(e));
orderEvents.subscribe((e) => updateAnalytics(e));
orderEvents.subscribe((e) => alertWarehouse(e));

orderEvents.emit(new OrderPlaced(orderId)); // publisher knows nothing

How fan-out flows

flowchart LR
    P[Publisher<br/><i>OrderPlaced</i>]
    B((Event bus<br/>/ broker))

    P -->|emit| B

    B --> S1[Email<br/>subscriber]
    B --> S2[Analytics<br/>subscriber]
    B --> S3[Warehouse<br/>subscriber]
    B --> S4[Slack<br/>subscriber]
    B -.add later.-> S5[Loyalty<br/>subscriber]

    style P fill:#0e7490,stroke:#06b6d4,color:#fff
    style B fill:#9a3412,stroke:#f97316,color:#fff
    style S1 fill:#365314,stroke:#84cc16,color:#fff
    style S2 fill:#365314,stroke:#84cc16,color:#fff
    style S3 fill:#365314,stroke:#84cc16,color:#fff
    style S4 fill:#365314,stroke:#84cc16,color:#fff
    style S5 fill:#1c2333,stroke:#475569,color:#aab3c4,stroke-dasharray: 5 5

The publisher emits once. The bus fans out. Subscribers can be added or removed without anyone else noticing.

Scaling out: from EventEmitter to Kafka

The same pattern, three orders of magnitude apart:

flowchart TB
    subgraph Inproc [In-process Observer]
        P1[Publisher object]
        L1[Listener A]
        L2[Listener B]
        L3[Listener C]
        P1 -->|sync method call| L1
        P1 -->|sync method call| L2
        P1 -->|sync method call| L3
    end

    subgraph Distributed [Distributed Pub/Sub]
        P2[Publisher service]
        K[(Kafka topic<br/>partition 0..N)]
        C1[Consumer<br/>service A]
        C2[Consumer<br/>service B]
        C3[Consumer<br/>service C]
        P2 -->|append| K
        K -->|pull + offset| C1
        K -->|pull + offset| C2
        K -->|pull + offset| C3
    end

    style P1 fill:#0e7490,stroke:#06b6d4,color:#fff
    style L1 fill:#365314,stroke:#84cc16,color:#fff
    style L2 fill:#365314,stroke:#84cc16,color:#fff
    style L3 fill:#365314,stroke:#84cc16,color:#fff
    style P2 fill:#0e7490,stroke:#06b6d4,color:#fff
    style K fill:#9a3412,stroke:#f97316,color:#fff
    style C1 fill:#365314,stroke:#84cc16,color:#fff
    style C2 fill:#365314,stroke:#84cc16,color:#fff
    style C3 fill:#365314,stroke:#84cc16,color:#fff
PropertyIn-process ObserverDistributed Pub/Sub
CouplingDecouples identityDecouples identity, time, location
DeliverySynchronous, in-threadAsync, durable, possibly cross-region
Failure modeCrash kills all listenersBroker retains events; subscribers replay
OrderingMethod-call orderWithin-partition only
ExamplesDOM events, EventEmitter, RxJSKafka, RabbitMQ, NATS, SNS+SQS, EventBridge

Topic vs queue — the two shapes

At-least-once delivery + idempotency

Pitfalls

When NOT to use Pub/Sub

📺 Video

A YouTube walkthrough — coming once the deep-dive video is published.

💻 Code

A 30-line build challenge with starter code, hints, and a reference implementation.

🎯 Common interview questions
Q1. Observer vs Pub/Sub — same thing or different?

Same idea, different scale. Observer is the in-process version — direct method calls on subscriber objects, synchronous. Pub/Sub typically refers to the cross-process / distributed version — events travel through a *broker* (Kafka, RabbitMQ, NATS, SNS), subscribers run in different processes, delivery is asynchronous and durable. The pattern is the same; the broker decouples *time* and *location* on top of decoupling identity.

Q2. What happens if a subscriber is slow or down?

That's the central question of every event system. In-process Observer — slow subscriber blocks the publisher unless you fan out on a thread pool. Distributed Pub/Sub — the broker buffers events; if the subscriber is offline, events queue up to a configured retention. Beyond retention, events are dropped or moved to a dead-letter queue. Backpressure (bounded queues + slow producer) is the only way to avoid unbounded memory growth.

Q3. How do you guarantee at-least-once delivery?

Subscribers acknowledge after successfully processing. The broker re-delivers any unacknowledged event. This means subscribers must be idempotent — same event arriving twice must produce the same result (use an event ID + dedup table, or design the operation to be naturally idempotent like upserts).

Q4. What's the difference between a topic and a queue?

Topic — fan-out, every subscriber gets a copy of every event (Kafka topics, SNS topics, RabbitMQ fanout exchanges). Queue — competing consumers, one event goes to one of N workers (SQS, RabbitMQ direct queues, Kafka consumer group). Pub/Sub typically means topic semantics; work distribution typically means queue semantics. Most brokers support both.

Q5. When should you NOT use Pub/Sub?

When you need an immediate synchronous answer (use direct RPC), when ordering across all events matters globally (Pub/Sub usually only orders within a partition), or when the workflow has a small fixed number of steps with strict transactions (orchestrated workflow engine like Temporal/Airflow may be a better fit). Pub/Sub shines when fan-out is high and consumers can be late or many.

↗ Related concepts

Comments 0

Discuss this page. Markdown supported. Be kind.

Loading…
Loading comments…