Mustaque Nadim Academy
Part 3 · Architecture

Publish / Subscribe

One new Adda post must ripple to the feed, search, notifications, and analytics. If the Posts service has to call each of them, it becomes everyone's bottleneck. Pub/sub lets it broadcast and forget.

The problem

A user publishes a post. That one fact needs to ripple everywhere: fan it into followers' feeds, index it for search, notify anyone mentioned, update the analytics dashboard, run it past moderation, award activity points. Today Adda's Posts service calls each of those services directly, one after another.

Every time the team adds a reaction — "also push a mobile alert," "also update the trending model" — someone edits the Posts service and redeploys it. Posts now imports and knows about eight other services. It's slow (it waits on all of them), fragile (if Notifications is down, the post itself fails), and the team that owns Posts has become a bottleneck for everyone who wants to react to a new post.

A first attempt

Push the calls onto a message queue so Posts doesn't block — the queue trick Adda just learned. Better: Posts enqueues a job and returns. But a plain queue is point-to-point: each message goes to exactly one consumer. To feed six different reactions you'd create six queues and have Posts write the same event into all six, one enqueue per consumer.

That's still Posts knowing its whole audience. Add a seventh reaction and Posts changes again. The coupling didn't go away — you just moved it. The producer is still responsible for knowing every consumer, which is exactly the knot you wanted to cut.

The insight

Flip who knows whom. Instead of the producer sending to N known consumers, the producer publishes one event to a named topic and forgets about it. Any service that cares subscribes to that topic and receives its own copy.

That's publish/subscribe. Posts publishes PostCreated once. Feed, Search, Notifications, and Analytics each subscribe independently. Adding a new reaction means adding a new subscriber — the producer never changes. Posts no longer knows or cares who's listening.

Pub/sub inverts the dependency

In direct calls, the producer depends on every consumer. In pub/sub, consumers depend on the event, not the producer. Producers and consumers evolve independently — the essence of event-driven architecture.

How it works

Producer publishes to a topic

Posts emits one message — PostCreated { postId, userId, body } — to the posts topic. It doesn't know how many subscribers exist, or any of them. Publish and move on.

The broker fans out

The broker copies that event to every subscription attached to the topic. One publish becomes N deliveries — one per interested service — without the producer lifting a finger.

Each subscriber consumes independently

Feed, Search, and Analytics each have their own subscription and their own backlog. A slow subscriber builds up its own queue; it doesn't slow the others or the publisher. Each processes at its own pace.

Add reactions without touching the producer

Marketing wants a push alert on every post from someone you follow? They stand up a service and subscribe to posts. The Posts service is never redeployed, never even told. The system grows at the edges.

Concrete numbers

Fan-out is the headline: 1 publish → N deliveries. A busy topic on Kafka or Google Pub/Sub handles hundreds of thousands to millions of messages per second and delivers to each subscriber group in parallel, typically within tens of milliseconds end to end.

Compare the coupling cost directly. Direct calls to 6 services in sequence, each ~20 ms, make the post request wait ~120 ms and fail if any one is down. Publish to a topic and Posts waits ~2 ms for the broker ack and always succeeds regardless of a subscriber being down — that subscriber just processes its backlog once it recovers. You traded synchronous certainty for asynchronous resilience.

When to use it

Fan-out means no single 'it's done' moment

After Posts publishes, the search index may update in 50 ms and the notification may fire 3 s later. There's no instant where you can say "the whole post finished," because each subscriber runs on its own clock. If a step must complete before you respond to the user (like saving the post itself), keep that synchronous — publish only the reactions that are genuinely fire-and-forget.

Ordering and duplicates are still your problem

Global ordering across a topic is usually not guaranteed — only within a partition/key. If PostUpdated can arrive before PostCreated, subscribers must handle it (partition by postId to keep one post's events in order). And like queues, pub/sub is at-least-once: subscribers must be idempotent, because every subscriber can receive the same event twice.

Practice

Recap

  • Pub/sub inverts the dependency: producers publish one event to a topic and don't know their subscribers; consumers depend on the event, not the producer.
  • One publish fans out to every subscriber, and each consumes on its own independent backlog — so you add reactions by adding subscribers, never by touching the producer.
  • The costs are no single "done" moment, weak cross-topic ordering, and at-least-once delivery — keep must-complete steps synchronous and make subscribers idempotent.

In an interview

How to discuss this

Frame pub/sub as "this happened" events versus a queue's "do this task." The line that scores points: the producer publishes once and doesn't know its subscribers, so new reactions are added without redeploying the producer. Then volunteer the hard parts — no single completion moment, ordering only within a partition key, and at-least-once duplicates — and say keep the critical write synchronous while everything else reacts asynchronously.

How is this guide?

Last updated on

On this page