Design problems
Social, feeds & contentHardSDE-2 30 min

Design live comments and reactions for live streams

Asked at
AmazonDream11GoogleJioHotstarMetaShareChat

The interviewer asks

“Design live comments and emoji reactions for a live stream, like chat during an India vs Pakistan match on JioHotstar or a YouTube Live. Tens of millions of people watch the same stream; comments and reaction bursts should appear for viewers within a couple of seconds.”

What a strong answer looks like

The core insight is that this is a fan-out problem with a lossy, human-sized output. No viewer can read 50,000 comments a second, so a strong design sends every viewer a sampled, moderated stream of a few comments per second, and turns millions of reactions into aggregated counts per second rather than individual events. Viewers hold a persistent connection (WebSocket or SSE) to a fleet of edge gateways; each gateway subscribes once per stream to a pub/sub channel, so fan-out is two-level: one message to hundreds of gateways, then each gateway pushes to its tens of thousands of local connections. Writes go through rate limiting, moderation and an aggregator. Durability is weak by design for reactions, stronger for comments that are stored for replay.

Concepts to mention

Persistent connections: WebSocket or SSEStateless connection gatewaysTwo-level fan-out via per-stream pub/sub channelsComment sampling and prioritisationReaction aggregation into per-second countsClient-side batching of reactionsRate limiting and slow modeFast sync moderation + async heavy moderationDifferent durability for comments vs reactionsReconnect with jittered backoff

1 Requirements

Functional

  • Viewers post text comments on a live stream and see others' comments in near real time
  • Viewers send emoji reactions; everyone sees bursts (for example floating hearts) and totals
  • Moderation: block abusive comments, slur filters in Indian languages, user mute and ban
  • Pinned comments and highlighted comments from hosts or verified users
  • Comments stored so that a replay (VOD) shows them at the right time offset

Non-functional

  • Up to 50M concurrent viewers on one stream (a big cricket match), 10k+ streams in parallel on a normal day
  • End-to-end delivery under 2 s p95 from post to other viewers' screens
  • Write rate: up to 100k comments/s and 2M reactions/s on one hot stream
  • Losing a few reactions is acceptable; accepted comments should not be lost for replay
  • Chat failure must never affect video playback

Out of scope (say it out loud)

  • Video streaming pipeline itself
  • Private one-to-one messaging
  • Paid super-chat payments (mention as extension only)

2 Estimates

Concurrent connections~50M on one streamBig cricket matches in India have reached tens of millions of concurrent viewers; design for 50M sockets on one stream as the upper bound
Gateway fleet~500-1,000 serversA tuned WebSocket server holds ~50k-100k idle connections; 50M ÷ 100k = 500, ×2 for headroom and zone loss
Comment writes~100k/s peakIf 0.2% of 50M viewers comment in the same second after a wicket = 100k/s
Comments shown per viewer~5-10/sHumans cannot read more; the rest are sampled out of the broadcast stream
Outbound frames~100M frames/s (~1.6 Tbps)Sending each comment separately would be 50M × 10 = 500M messages/s; batching into 2 frames/s gives 100M frames/s; × ~2 KB = ~200 GB/s ≈ 1.6 Tbps across the gateway fleet
Reactions~2M/s in, 1 aggregate frame/s out per viewerAggregated per stream per second: {heart: 120k, fire: 80k} instead of 2M individual messages
Comment storage~5 GB per big match~20M accepted comments × ~200 bytes; stored by (stream_id, time bucket)

3 API

EndpointWhat it does
WS/v1/live/{stream_id}/connectSubscribe to comment and reaction frames for a stream; server pushes batched frames
POST/v1/live/{stream_id}/commentsPost a comment (text, client_msg_id for idempotency); returns accepted or rejected
POST/v1/live/{stream_id}/reactionsSend reactions; client batches taps, e.g. {heart: 7} every 2 s
GET/v1/live/{stream_id}/comments?from_offset=&to_offset=Fetch stored comments for replay or for catch-up after reconnect
POST/v1/live/{stream_id}/moderationHost or moderator actions: pin, delete, mute user, slow mode

4 Data model

commentsCassandra / ScyllaDB
stream_idbucket (minute)comment_id (time-ordered)user_idtextstream_offset_msstatus

Partition by (stream_id, minute bucket) to avoid one giant partition per match; clustering by comment_id for time order. TTL for non-replay streams.

stream channel stateRedis
stream_id → pinned commentslow-mode secondsviewer countbanned users set

Small, hot, read by every write path request.

user moderationPostgres + Redis cache
user_idglobal banmute_untiltrust_score

Trust score used to sample and prioritise comments.

reaction aggregatesIn-memory in aggregators, snapshots to Redis
stream_idsecondemoji → count

Totals persisted every few seconds; individual taps are never stored.

pub/sub channelsKafka (ingest) + Redis pub/sub or NATS (fan-out)
topic per stream shard / channel per stream_id

Kafka for durable ingest; light pub/sub for fan-out to gateways.

5 Architecture

CLIENTSEDGESERVICESASYNCDATAWS connectPOST commentrate limit, bansfast checksacceptedpersist1 frame/sper-stream channelpush framesViewer appsL4 load balancerWebSocket gatewaysComment write APIModeration (rules +ML)Kafka (per-streamtopic)Sampler / aggregatorFan-out pub/subComments (Cassandra)Redis (stream state)
ClientEdge / gatewayService / workerQueue / streamCacheDatabase / storageAI / modelThird partyrequestasync (event / queue)

Hover a box to highlight its connections.

6 Request flows

Post a comment

  1. 1Viewer sends POST /comments with text and client_msg_id over HTTP (or over the same WebSocket)
  2. 2Write API checks auth, ban and mute lists and slow-mode in Redis, and applies a per-user rate limit (e.g. 1 comment per 3 s)
  3. 3Fast moderation: blocklist and regex for slurs in English, Hindi and Hinglish, link spam, repeated text; an ML toxicity model with a ~20 ms budget
  4. 4Accepted comment is written to Kafka, partitioned by stream_id (hot streams are split across several partitions by hashing user_id)
  5. 5The sender sees their own comment immediately (optimistic local echo), whether or not it is sampled for others
  6. 6A consumer writes the comment to Cassandra with its stream offset for replay

Deliver comments and reactions to 50M viewers

  1. 1Sampler/aggregator for the stream consumes the Kafka partitions and every ~500 ms builds a frame: up to ~5 comments chosen by priority (host, verified, high trust, diversity) plus reaction counts
  2. 2It publishes the frame once to the stream's pub/sub channel
  3. 3Each gateway with at least one viewer of that stream is subscribed to that channel, so ~1,000 gateways receive the frame, not 50M connections
  4. 4Each gateway serialises the frame once and writes it to all its local connections for that stream
  5. 5Client animates reactions based on counts (e.g. scale the number of floating hearts) and appends comments

Reconnect after network drop

  1. 1Client reconnects with exponential backoff and jitter to avoid a thundering herd
  2. 2It sends the last frame sequence number it saw
  3. 3Gateway resumes live frames; optionally the client fetches the last 30 s of stored comments from the read API; missed reactions are simply skipped

7 Deep dives

Two-level fan-out and the gateway fleet

Pushing directly from one service to 50M sockets is impossible, so the fan-out is hierarchical. Viewers connect through an L4 load balancer to stateless WebSocket gateways (each holding ~50k-100k connections using epoll-based servers in Go, Rust, Java Netty or Erlang). Each gateway keeps a local map stream_id → set of connections. When a gateway gets its first viewer for a stream, it subscribes to that stream's channel on the pub/sub layer; when the last one leaves, it unsubscribes. So the publish fan-out is 1 message → number of gateways (hundreds), and each gateway does the local fan-out with a single serialisation of the frame. For a stream with 50M viewers the pub/sub cost is trivial; the gateways' network output is the real load: at 2 KB per frame and 2 frames/s, 50M viewers need ~200 GB/s of egress across the fleet, which is why frames are batched and compressed (permessage-deflate) and why the frame rate is capped. For very large events add one more level: regional relay nodes subscribe to the core channel and re-publish to gateways in their region, which also keeps cross-region traffic low.

Sampling comments: what to show when 100k arrive per second

Every viewer receiving every comment is both impossible and useless. The sampler per stream receives all accepted comments and picks a small set per tick (for example 5 per 500 ms). Selection uses priority tiers: pinned and host messages always; verified users and high-trust accounts next; then a random sample from the rest, weighted to avoid duplicates (collapse 10,000 'what a shot!!' into one) and to mix languages. Different viewers can get different samples: shard viewers into ~16 cohorts and generate a slightly different frame per cohort, so more distinct comments get seen overall, while each frame is still shared by millions. The sender always sees their own comment locally, which keeps the experience feeling responsive. For small streams (under ~1,000 comments per minute) the sampler just passes everything through. This is the same idea as load shedding: you degrade gracefully by design rather than falling over. Mention it explicitly; interviewers look for the candidate who notices the human reading limit.

Reactions as aggregated counters

Two million taps per second cannot be individual messages. Clients batch locally: tap 15 hearts in 2 seconds, send {heart: 15} once. The write path does not even need Kafka durability for reactions: send them to aggregator nodes sharded by stream_id (and sub-sharded for hot streams), which keep in-memory counters per second. Every 500 ms to 1 s each shard emits partial counts; a merger combines them and puts {heart: 120k, six: 40k} into the next frame. Clients turn counts into animation density, capped so the UI stays smooth. Totals are snapshotted to Redis every few seconds for display and analytics. If an aggregator dies, you lose at most a second of reactions, which nobody notices. This is a deliberate choice: weak durability for high-volume, low-value events, strong durability for comments that appear in replays and can be reported. Showing you understand that different data in the same feature deserves different guarantees is a strong signal.

Moderation at live speed

Moderation must fit in the write path budget (tens of milliseconds) because sampled comments go out within a second. Layer it. First, cheap rules: blocklists for abusive words in English, Hindi, Tamil and others including romanised spellings and leetspeak, URL and phone number blocking, repetition and flood checks. Second, a small toxicity classifier served on CPU with a strict timeout; if it times out, the comment is still stored but marked not eligible for sampling. Third, user-level controls: per-user rate limits, slow mode for the whole stream during spikes, new-account restrictions, and a trust score. Fourth, asynchronous heavier models and user reports that can retroactively delete comments; deletions are broadcast as a small 'remove comment_id' event. Because only a sampled fraction is shown, the sampler can prefer high-confidence clean comments, which further reduces risk. Legal requirements in India (IT Rules) mean you need takedown audit logs too.

8 Trade-offs

WebSocket vs SSE vs long polling

Viewers mostly receive, so SSE over HTTP/2 is enough and simpler through proxies; posts go over normal HTTP. WebSocket gives bidirectional traffic on one connection and is the common choice in apps. Long polling multiplies request load and is a fallback only. Either way the gateway architecture is the same.

Kafka vs Redis pub/sub for fan-out

Kafka is durable and great for ingest and persistence, but having 1,000 gateways each consume per-stream topics is awkward. Redis pub/sub or NATS is fire-and-forget and very fast for fan-out. Use Kafka for ingest and storage, light pub/sub for the broadcast hop; frames lost there are acceptable.

Everyone sees the same comments vs per-cohort samples

One global sample is simple and consistent; per-cohort sampling shows more distinct comments overall and spreads attention. Since there is no 'correct' set of comments to show, cohorts are a cheap win.

Sync moderation vs async

Full ML moderation synchronously adds latency and a dependency; fully async lets abuse show for seconds to millions. A fast sync layer plus async heavy checks with retroactive deletion balances both.

9 Bottlenecks

Connection storm at the start of a match or after an outage

Millions reconnect within seconds. Use jittered exponential backoff on clients, connection rate limits per gateway, pre-scaled gateway fleets before scheduled events, and let gateways accept connections before subscribing to streams lazily.

One hot Kafka partition per stream

A single partition caps throughput. Split the hot stream's topic into many partitions keyed by user_id and have the sampler merge them; ordering across comments does not matter at the second level.

Gateway egress bandwidth

Output dominates. Batch frames, compress, cap frame size and frequency, and drop to fewer comments per frame under load.

10 Failure handling

Gateway crash

Its 100k clients reconnect via the load balancer to other gateways with jittered backoff and resume from the latest frame. No state is lost because gateways are stateless.

Pub/sub node failure

Run the fan-out layer clustered with channels sharded across nodes; gateways resubscribe on failover. A few seconds of frames may be lost, which is acceptable for live chat.

Comment pipeline overloaded

Turn on slow mode automatically, tighten rate limits, raise the sampling threshold, and keep video playback completely independent so chat issues never affect the stream.

11 What impresses

  • Noticing that humans can read only a few comments per second and designing sampling around it
  • Computing gateway count and egress bandwidth, not just QPS
  • Treating reactions as counters, not messages
  • Explaining why chat failure must be isolated from video playback
  • Per-cohort sampling to show more distinct comments
  • Pre-scaling for scheduled events like IPL finals

12 Mistakes

  • Broadcasting every comment to every viewer
  • Storing every reaction tap in a database
  • One service pushing directly to millions of sockets with no gateway tier
  • Ignoring reconnect storms
  • No moderation in the write path
  • Using a single Kafka partition per stream and not noticing the throughput cap

13 Follow-ups

Q1How do you show comments in sync with the video when viewers are 10-30 s behind live?
Q2How would you add paid highlighted comments (Super Chat)?
Q3How do you count concurrent viewers accurately?
Q4What changes for a small stream with 200 viewers?
Q5How would you replay comments with a recorded VOD?
Q6How would you detect coordinated spam bots?

Saved on this device. Create a free account to keep it on every device.

Practise this with Sam

A real mock interview, out loud, with an honest scorecard. Free to start.

Start free