InterviewKata
System design · Worked design D22 of 46 · Data and infrastructure · Long form

Design live comments

A cricket final is streaming live to five million people, and thousands of them are typing at once. Every viewer should see new comments within a couple of seconds, scroll back through what they missed, and never see abuse that moderators removed. The difficulty isn't storing comments. It's delivering one stream of them to millions of open connections, and deciding what a human can actually read.

1. The prompt

"Viewers of a live video can post comments. Everyone watching sees new comments appear in near real time. Design it."

The write side is modest; the read side is a fan-out problem. A comment written once may need to reach millions of screens within two seconds, over connections that drop whenever a phone goes into a tunnel. A good answer separates storing comments, routing them to the right servers, and delivering what a person can read.

2. Frame the problem

Ask these in the first five minutes. Each answer changes the design; guess before you open it.

How big is the biggest stream?

Most streams have a few dozen viewers. The largest events reach about 5M concurrent viewers.

The design has to cover both a long tail of tiny streams and a few huge ones. The huge ones decide the fan-out design.

How fast must a comment appear for others?

Within about 2 seconds for most viewers.

Two seconds rules out per-viewer database polling and gives a budget to split between write, routing and delivery.

Must every viewer see every comment?

On small streams, yes. On a stream with thousands of comments a second, nobody can read them all, so showing a representative, relevant subset is fine.

This is the most important question. It turns an impossible fan-out into a manageable one.

Do comments need a strict order?

Roughly in time order for everyone. Two comments a second apart may appear in either order.

We can avoid a global sequencer and use per-stream, time-ordered ids.

Can viewers scroll back, and do comments outlive the stream?

Yes. Viewers can load earlier comments, and comments replay alongside the recording afterwards.

Comments must be stored durably, keyed by stream and time, with the video timestamp kept for replay.

Are there replies, reactions, or only flat comments?

Flat comments now. Reactions (hearts, emoji) exist but are counted, not shown one by one.

Reactions are aggregated counters (Design D17 style), not messages.

What moderation is needed?

Banned users and blocked words must never appear. A moderator or classifier can remove a comment, and it must disappear from screens within seconds.

We need a fast pre-publish check, and "delete" events that travel the same path as comments.

Which clients?

Phones (often on mobile data), browsers and TVs.

Connections drop constantly, so reconnect-and-resume is a core path. Bandwidth per viewer matters.

3. What it must do

  1. A viewer can post a comment on a live stream.
  2. Viewers see new comments as they arrive, without refreshing.
  3. A viewer can load earlier comments by scrolling back, during and after the stream.

Not now (with a reason):

4. How well it must do it

TargetWhy this number
Fresh: 99% of viewers see a new comment within 2 s of posting speedLive chat feels broken when replies arrive after the moment has passed.
Scale: 50M concurrent viewers in total, 5M on one stream, 200,000 comments a second at peak across all streams load shapeBig events are bursty: a goal or a wicket makes comments jump tenfold in seconds.
Available over consistent: during failures, viewers keep seeing comments, even if a few arrive late or out of order consistency or availabilityA frozen feed is worse than a slightly imperfect one. No money or safety depends on exact order.
Kept: an accepted comment is stored and visible in scrollback and replay data safetyThe creator and moderators rely on the record. A few seconds of delivery loss is tolerable; storage loss isn't.
Safe: banned users and blocked content never appear; removed comments vanish from screens within 5 s security · rulesLarge live events attract abuse, and platforms have legal duties to act on reported content.
Light on phones: under ~5 KB/s of comment data per viewer where it runsViewers are already streaming video on mobile data. Comments must not compete with it.

5. Numbers that change the design

QuantityWorking→ so we…
Open connections50M viewers; a tuned server holds 50–100K long-lived connections~500–1,000 connection servers → so delivery is a fleet of stateful servers, separate from the comment API
Naive fan-out on the biggest stream5M viewers × 500 comments/s2.5 billion deliveries a second for one stream → so we must not send every comment to every viewer
What a human can readComfortably 5–10 short comments a second→ so cap each viewer at ~10 comments/s, batched once a second: 5M messages/s for the stream, spread over ~100 servers
Bandwidth per viewer10 comments × ~250 bytes once a second~2.5 KB/s: within the phone target
Writes200,000 comments/s × ~300 bytes~60 MB/s, ~5 TB a day → so a partitioned log in front of a wide-column store, partitioned by stream and time bucket

Skip estimating total storage for years or API calls for history. They're ordinary, and nothing in the design depends on them.

6. The nouns

comments  partition (stream_id, minute_bucket) · sort comment_id (time-ordered, e.g. Snowflake)
          user_id, text, video_offset_ms, status (visible | removed), score
streams   stream_id, owner_id, status, started_at, viewer_count (approximate)
bans      (stream_id or global, user_id) → until

7. The contract

Posting is an ordinary REST call. Live delivery is one-way from server to viewer, so Server-Sent Events fit: plain HTTP, built-in reconnection, and the browser resends the last event id it saw, so the server can resume. WebSockets work too, and are the choice if the same connection must also carry chat typing indicators or other client-to-server traffic. The user always comes from the session token.

POST /v1/streams/{stream_id}/comments          {text, client_id: "c-4f1…"}
     → 201 {comment_id, created_at}             (client_id dedupes a retried post)
GET  /v1/streams/{stream_id}/comments?before={comment_id}&limit=50   → older comments (scrollback)
GET  /v1/streams/{stream_id}/live               Accept: text/event-stream
     ← id: 7244031  event: comments  data: [{id, user, text}, …]     (a batch about once a second)
     ← id: 7244032  event: removed   data: ["7243990"]
     ← event: stats data: {viewers: 4812330, hearts_per_s: 9100}
DELETE /v1/comments/{comment_id}                (author or moderator)

Data flow: (1) a comment is checked and accepted → (2) stored, and appended to the stream's log partition → (3) routed to the connection servers that hold viewers of that stream → (4) each server batches, samples and sends once a second → (5) viewers that reconnect resume from their last event id.

8. First working version

Start with an API service, one database and a few connection servers. Get all three requests working, and note what won't scale.

8.1 Posting a comment

The API checks the user isn't banned, rate-limits them (say 1 comment every 2 s per user per stream), assigns a time-ordered id and writes the comment. The client_id makes a retried post (the phone lost the response) return the same comment instead of a duplicate.

8.2 Watching live (version one)

The viewer opens an SSE connection to a connection server. In this first version, the server asks the database once a second for comments newer than the last one it sent to each viewer, and pushes them.

8.3 Scrolling back

A history request reads the newest partition (stream_id, current minute) backwards from the given id, then moves to earlier minute buckets until it has 50 comments. A CDN can cache history pages of ended streams, because they never change.

Parked for later (section 9 fixes each)

  1. Each server polling the database for each of its viewers: 50M queries a second.
  2. A 5M-viewer stream receiving 500 comments a second would mean billions of deliveries.
  3. All writes for the biggest stream land on one database partition.
  4. Phones reconnecting lose the comments sent while they were away, or get them twice.
  5. Abusive comments go out before anyone can stop them; removed ones stay on screens.
  6. Deploying or losing a connection server drops 50,000 viewers at once.

9. Make it hold up

9.1 Getting each comment to the right servers fresh · scale

ApproachWhy
✗ BadEvery connection server polls the database for every viewerLoad grows with viewers × polls a second. The database falls over long before 50M viewers.
~ GoodEvery connection server subscribes to the whole comment stream and keeps only what its viewers needNo polling, but 1,000 servers each receiving 200,000 comments a second is 200M messages a second of mostly wasted work.
✓ GreatTwo-level fan-out by stream: comments are published to a per-stream channel; a connection server subscribes to a channel only while it has viewers of that stream, then fans out locally to its own connections. Route viewers of the same stream to the same group of servers (consistent hashing on stream_id) so each channel has few subscribers.Each comment crosses the network once per server that needs it, not once per viewer. Small streams live on one server; a huge stream spreads over a group sized to its audience.

The channel layer can be Redis pub/sub (fast, fire-and-forget) or a log such as Kafka (replayable, with retention). A common split: Kafka partitions hold the durable comment log; a lightweight pub/sub tier does the live fan-out to connection servers; the log is the source for recovery.

9.2 The five-million-viewer stream fresh · light on phones

During a big moment, a top stream can receive 500 or more comments a second. Delivering them all would cost billions of messages a second, and no human could read them anyway. The answer is product and engineering together:

The lab in section 13 shows the difference on a phone screen.

9.3 Storing comments without a hot partition kept · scale

ApproachWhy
✗ BadA relational table with an index on (stream_id, created_at), one primary200,000 writes a second on one primary is too many, and the biggest stream's index pages become a hotspot.
~ GoodA wide-column store (Cassandra, DynamoDB) partitioned by stream_id, sorted by comment idScales across streams, but one huge stream is one partition. A DynamoDB partition, for example, accepts up to 1,000 write units a second.
✓ GreatPartition by (stream_id, minute_bucket), and write through a log: the API appends to a Kafka partition (by stream), and a consumer writes to the store in batchesEach minute of a busy stream is a new partition, so writes spread over time. The log absorbs the spikes after a goal, and delivery can read from the log immediately while storage catches up.

9.4 Ordering, reconnects and duplicates fresh · kept

9.5 Moderation that keeps up safe

ApproachWhy
✗ BadPublish everything and rely on viewer reportsOn a big stream, abuse reaches millions of screens before anyone reports it.
~ GoodSynchronous checks before accepting a comment: ban list, rate limit, blocked wordsFast (a few milliseconds) and stops the obvious cases, but misses anything subtle.
✓ GreatSynchronous checks, plus an asynchronous classifier that scores every comment within about a second. On big streams, hold comments until the score is in (the sampler prefers high scores anyway); on small streams, publish immediately and remove if flagged. Removals travel as removed events on the same channels.Safety where the audience is largest, speed where it's smallest. Removal reaches every screen in the same ~2 s as comments.

Also give creators their own moderators, slow mode (a longer per-user rate limit), "followers only" mode, and shadow bans (the banned user still sees their own comments, so they don't simply create a new account).

9.6 Running the connection servers scale · surviving failures

9.7 The whole design after the deep dives

Follow one comment through every part: checks, the log, storage, per-stream channels, connection servers that batch and sample, and the viewers' phones.

10. Interviewer follow-ups

Expect several of these after the main design. Give the mechanism and its cost in under a minute.

A World Cup final: 50M viewers on one stream.

Sampling already caps each viewer at ~10 comments a second, so delivery scales with viewers: 50M messages a second over ~1,000 servers. Add a third fan-out level: regional relay servers subscribe once to the stream channel and feed the connection servers in their region, so the channel has dozens of subscribers instead of a thousand. Pre-scale before kick-off; the schedule is known.

A region goes down.

Its viewers reconnect to the next region through DNS or anycast failover, with jittered retries. Comments written there and already in the replicated log still appear; the last second or two may be lost from live delivery but are in storage if the write was acknowledged. Availability wins: viewers see the feed again within seconds.

The streamer posts a comment. Does everyone see it?

Yes. The sampler always includes the creator's and moderators' comments, and the client can pin them. Priority rules beat random sampling.

Comments should replay alongside the recording.

Store video_offset_ms with each comment (the position in the stream when it was posted). The replay player requests comments by offset range as the video plays: "offsets 120,000–150,000 ms". Precompute sampled replay pages for huge streams and serve them from a CDN.

Why not send comments as push notifications?

Push notifications are for waking a device that isn't connected, with delivery in seconds to minutes and strict rate limits. Live comments need sub-second, high-rate delivery to an app that's open, which is exactly what an open SSE or WebSocket connection gives.

Why SSE and not WebSockets?

Traffic is almost all server-to-client; posting is a normal POST. SSE is plain HTTP (friendly to proxies and HTTP/2), reconnects by itself and resends the last event id, so resume is nearly free. Choose WebSockets if the connection must carry two-way traffic, such as typing indicators or interactive polls.

How do you show the live viewer count?

Each connection server knows its connection count per stream. Every few seconds it reports to an aggregator, which sums them and publishes stats events. It's approximate by design; nobody needs the exact count.

A legal request: remove every comment by a user, everywhere.

Mark the user banned, emit removed events for their comments on live streams, and run a background job that sets status = removed in storage and purges cached history pages. Log the action for audit.

Costs must drop by half.

Lower the per-viewer cap during calm periods (5 a second is still plenty), batch every 2 s on TV clients, compress batches, move ended streams' comments to cheaper storage, and keep small streams on shared servers. Bandwidth to clients is usually the biggest bill, so the cap is the biggest lever.

Bots flood a stream with near-identical spam.

Per-user and per-IP rate limits (Design D02), near-duplicate detection on text (hash after normalising), account-age and reputation checks, and automatic slow mode when the spam rate spikes. The classifier's score also pushes spam out of the sample.

Make it persistent community chat instead.

Then history matters as much as live delivery: messages are stored by channel and time window, members have read markers, and big servers push full messages only to members viewing the channel. Design D07's community servers variation walks through it.

11. When things break

FailureWhat users seeHow we detect itWhat limits the damage
A connection server diesIts viewers' feeds pause for a few secondsConnection count drops; health checksClients reconnect with jitter and resume from Last-Event-ID
Pub/sub node failsSome streams pause brieflyPublish errors; subscription lossServers resubscribe on a replica; gaps refilled from the log
Storage slows downLive feed fine; scrollback slowWrite lag from the log consumerDelivery reads from the log, not storage; consumer catches up
Classifier is downBig streams' comments delayed or reducedScoring latency alertFall back to synchronous checks plus stricter sampling and slow mode
A goal triggers 10× commentsNothing, ideallyComment rate per streamLog absorbs the burst; sampling caps delivery; pre-scaled servers
Reconnect storm after an outageSlow recoveryConnection attempts per secondJittered client backoff, server retry: hints, admission control

12. Wrap-up and the clock

Trade-offs we made:

What we'd watch:

At 10×:

A 60-minute interview

0–5Frame the problem: biggest stream, "must everyone see everything?"
5–10Must do, how well, the fan-out numbers
10–15Nouns and contract (SSE vs WebSockets)
15–28First working version 8.1–8.3, parked list
28–50Deep dives: two-level fan-out (9.1), the huge stream (9.2), resume and order (9.4)
50–60Moderation or storage, then follow-ups and wrap-up

A 90-minute interview

0–8Frame the problem, including moderation and replay
8–15Must do, how well, numbers
15–22Nouns, contract, data flow
22–38First working version
38–75Deep dives 9.1–9.6 and the final picture
75–85Follow-ups: 50M viewers, region loss, cost
85–90Trade-offs, metrics, 10× plan
LevelWhat a strong answer shows
Mid-levelA comment API, a store keyed by stream and time, persistent connections (SSE or WebSockets) and pub/sub instead of polling; history with cursors.
SeniorTwo-level fan-out by stream with routing by stream_id; the huge-stream insight (batch and sample); hot partitions fixed with time buckets and a log; resume with event ids; pre-publish checks and removal events.
Staff+Frames the product trade-off (what people can read) with numbers, relay tiers for mega-events, moderation latency vs audience size, connection-fleet operations (draining, reconnect storms), cost per viewer and regional failover.

Common mistakes:

13. Try it, check yourself, read more

The comment firehose. The phone shows comments arriving at the rate this viewer would actually receive them. The numbers show what the servers must send.

LIVEFinal

Illustrative capacity: one connection server holds 50,000 viewers and sends up to 500,000 messages a second; a comment is ~250 bytes.

How does a comment reach millions of viewers without polling?

Each comment crosses the network once per server that needs it; servers fan out locally.

A stream has 5M viewers and 500 comments a second. What do you send each viewer?

Nobody can read 500 a second; sampling and batching make delivery scale with viewers, not viewers × comments.

A phone's SSE connection drops for 20 seconds. How does it catch up?

Every batch has an id; resume is built into the EventSource protocol.

How do you stop the biggest stream from overloading one storage partition?

Time buckets spread one stream's writes over many partitions; the log absorbs spikes.
ResourceWhy
Instant messaging at LinkedIn: scaling to hundreds of thousands of persistent connections (LinkedIn)SSE connection servers in production, and the limits they hit.
Server-sent events (HTML standard)Event ids, Last-Event-ID and retry.
Design Facebook live comments (Hello Interview)Another walkthrough of the same problem.