Art of Codeartofcode
LLeeaarrnniinngg
BBlloogg
TToooollss
GGaammeess AArreennaa
JJoobbss
LLiivvee CCoouurrsseess

Courses

  • System DesignHigh-level system design, one everyday scene at a time: 30 concepts and 15 interview rounds.712 lessons
All coursesTutorialsMy Learning

Categories

  • 01AI & Tools
  • 02Tutorials
  • 03Engineering
  • 04Reviews
  • 05Build Logs
  • 06Tales of Misfits
  • 07Opinion
  • 08Career
  • 09News

Read & write

All storiesEvery post, newest first.Write for usPublish your stories and earn from what you write.Writer leaderboardsWho is writing the most read posts.

Categories

  • PDF Tools25
  • Development09
  • Utilities10
  • Design04
  • Converters04
  • Generators02

Featured

JSON FormatterFormat, validate and minify JSON.Regex TesterTest patterns against live text.Merge PDFCombine PDFs in your browser.JWT DecoderRead a token’s header and claims.Password GeneratorStrong passwords, generated locally.Gradient GeneratorBuild CSS gradients visually.
All 53 tools

Play a round

DevdleOne developer word a day, six guesses.Python RushClassic snake, with coding icons to eat.Blink or BreakA quick test of your reflexes.
Games Arena

Learn with us

Live CoursesInstructor-led courses. Join a waitlist and be first in when one starts.LearningStructured programming courses with AI assistance and certificates.

System Design

23. Pub/Sub and Fan-Out

Lesson 12 of 14

23. Pub/Sub and Fan-Out

1
The picture to keep in your head
2
23.1 What's the difference between a queue and pub/sub?
3
23.2 What are topics and subscribers?
4
23.3 What are consumer groups?
5
23.4 What are the fan-out strategies?
6
23.5 What is event-driven architecture?
7
23.6 How do you handle duplicates and ordering?
8
23.7 How do you publish events reliably (the outbox pattern)?
9
Memory card and where the analogy breaks
10
Practice: five levels
11
Interview questions
12
Solutions
13
Sources
14
Code lab
23. Pub/Sub and Fan-Out

Solutions

3 min read+10 XP

Solutions

Level 1

reveal

Level 2

reveal

Level 3

reveal

Level 4

reveal

Level 5 (sketch)

reveal

No tracks available

Lofi Player

artofcode

artofcode.in

The den of devs.

Stories, tools, games, and gear for developers.

Read the StoriesEExxpplloorree TToooollss
Art of Codeartofcode

Platform

  • BBlloogg
  • TToooollss
  • GGaammeess AArreennaa
  • CCoouurrsseess
  • LLeeaarrnniinngg
  • CCaammppuuss

Company

  • AAbboouutt UUss
  • JJoobbss && IInntteerrnnsshhiippss
  • CCoonnttaacctt

Legal

  • TTeerrmmss ooff SSeerrvviiccee
  • PPrriivvaaccyy PPoolliiccyy
  • RReeffuunndd PPoolliiccyy

Copyright © 2026 artofcode.in, All Rights Reserved.

IST·--:--

1Show the Level 1 solution
2**A.**
31. **Queue**: a job for one worker.
42. **Topic**: a fact many services care about.
53. **Topic**: search, cache, recommendations and price alerts all react.
64. **Queue**: one email, sent once.
75. **Topic**: feeds, notifications and recommendations react.
8
9**B.**
101. **False.** Inside a group, each message goes to one member.
112. **True.** The feed is precomputed; reading is one lookup.
123. **False.** With fan-out on read, posting is one write for anyone; it's fan-out on **write** that struggles with celebrities.
134. **False.** A crash after the commit and before the publish loses the event.
145. **True.** The relay can publish, crash before marking, and publish again.
1Show the Level 2 solution
21. 5,000,000 ÷ 50,000 = **100 seconds**.
32. 10,000 × 200 = **2 million** feed writes a day.
43. 1,000,000 × 300 = **300 million** lookups a day (before caching).
54. 1,000 × 4 = **4,000** deliveries a second.
65. Up to **1 second** waiting for the next poll, plus about 20 ms to publish: about **1.02 seconds** worst case. CDC usually gets this lower.
1Show the Level 3 solution
2Tested solution (Python 3), the key parts:
3
4```python
5class Broker:
6    def subscribe(self, topic, group, member):
7        self.subs[topic].setdefault(group, []).append(member)
8
9    def publish(self, topic, msg):
10        for group, members in self.subs[topic].items():          # every group
11            m = members[self.rr[(topic, group)] % len(members)]  # one member each
12            self.rr[(topic, group)] += 1
13            self.inbox[m].append(msg)
14
15
16class Hybrid:
17    def post(self, author, book, t):
18        self.posts[author].append((t, author, book))
19        if author in self.celebs:
20            return                                   # celebrities: pull at read time
21        for u in self.followers[author]:
22            self.feeds[u].append((t, author, book))
23
24    def read(self, user, k=10):
25        items = list(self.feeds[user])
26        for a in self.following[user] & self.celebs:
27            items.extend(self.posts[a])
28        return sorted(items, reverse=True)[:k]
29
30
31class StockView:
32    def apply(self, event):
33        eid, book, version, qty = event
34        if eid in self.seen:
35            return "duplicate"
36        self.seen.add(eid)
37        if version <= self.version.get(book, 0):
38            return "stale"
39        self.version[book], self.stock[book] = version, qty
40        return "applied"
41```
42
43All checks pass: groups receive every event while members split them; push, pull and hybrid produce identical feeds for 400 readers, with the hybrid doing 44% fewer writes than push and 66% fewer reads than pull; the stock view ends at 8 after a stale and a duplicate event; the outbox keeps the order and its event together and republishes after a relay crash; both dual-write failures are reproduced. The full file is in `code/ch23_pubsub.py`.
1Show the Level 4 solution
21. **Ghost event:** the publish happens **inside** the database transaction, but the broker isn't part of that transaction. The event goes out immediately. Then the stock reservation fails, the transaction **rolls back**, and order 883 is never saved. The SMS service already received `order_placed` and sends the confirmation.
32. **Moving the publish after the commit** means the event goes out only for saved orders. But if the service crashes (or the broker is unreachable) **between** the commit and the publish, the event is **lost**: a real order with no SMS, no points, no packing slip.
43. **The outbox:**
5
6```python
7def place_order(db, order):
8    with db.transaction():
9        db.insert("orders", order)
10        db.insert("stock_reservations", reserve(order))
11        db.insert("outbox", {"event_id": f"order_placed:{order.id}",
12                             "topic": "order_placed",
13                             "payload": {"order_id": order.id}})
14    # a relay (or CDC) publishes outbox rows and marks them sent;
15    # subscribers de-duplicate by event_id
16```
17
18Now the event exists if and only if the order does, and it's published at least once.
1Show the Level 5 (sketch) solution
21. **Hybrid.** Authors under a threshold (say 100,000 followers) fan out on write to followers' feed lists; above it, releases are pulled at read time and merged. With 10,000 releases a day, mostly from small authors, pushes are cheap; the few huge authors cost one write each.
32. **Storage:** per-reader feed lists of **book IDs** (not full book data) in Redis or a wide-column store, capped at the latest 500 entries. 20 million readers × 500 IDs × 8 bytes ≈ **80 GB** before overhead, sharded by reader ID. Book details come from a cached catalogue lookup. Inactive readers' feeds can expire and be rebuilt on demand.
43. **Publishing:** the catalogue service writes the book and an outbox row in one transaction; CDC or a relay publishes `book_released` keyed by author ID.
54. **Duplicates and disorder:** the fan-out worker de-duplicates by event ID (feed insertion keyed by book ID is idempotent anyway); updates to a release (a corrected date) carry a version, and older versions are ignored.
65. **Other subscribers**, each its own consumer group: push notifications for followers (Chapter 33), search indexing (Chapter 19), recommendations, and analytics. Each can fail, lag or be redeployed without affecting the others or the catalogue service.