A live comments system lets everyone watching the same live video post comments and see each other's within about a second. The standard design writes each comment over plain HTTP to a comment service that stores it, then pushes it to viewers over a long-lived connection (Server-Sent Events or WebSockets), using pub/sub partitioned by live video ID so each server only receives comments for the streams its viewers are watching. Interviewers then dig into hot streams with huge audiences, catching up after a dropped connection, and moderation that does not slow the feed down.
1. Clarify requirements and size the problem
Functional: viewers post a text comment on a live video, see others' comments in near real time, and see recent comments (with scroll-back) when they join late. Leave threaded replies, reactions, search and the video pipeline out of scope unless asked.
Non-functional:
- Low latency. Hello Interview's public breakdown of this problem sets a target under 200 ms end to end; anything in the low hundreds of milliseconds is a reasonable number to propose.
- Availability over strict consistency. Two viewers seeing a slightly different subset or order is fine; the feed going dark is not.
- Heavy read skew and uneven load. Each comment is written once and delivered to every viewer. Most streams have a handful of viewers; a few have hundreds of thousands.
The number that matters is deliveries, not writes. Deliveries per second for one stream equal comments per second times concurrent viewers. LinkedIn engineer Akhilesh Gupta used this arithmetic in a QCon London 2020 talk on live-video likes: 100 likes per second on a video with 10,000 viewers is about one million deliveries per second. In the same talk, LinkedIn cited roughly 100,000 persistent connections per frontend machine, a useful figure for estimating how many connection servers a big event needs.
2. API and data model
Split the write path from the read path. Posting is an ordinary request; receiving is a stream.
# Write (user comes from the auth token, not the body)
POST /v1/videos/{videoId}/comments { "text": "this is great" }
-> 201 { "commentId": "...", "createdAt": "..." }
# Read: live stream of new comments
GET /v1/videos/{videoId}/comments/stream (SSE, or a WebSocket upgrade)
# Read: history for late joiners and scroll-back
GET /v1/videos/{videoId}/comments?before={commentId}&limit=50
| Field | Notes |
|---|---|
video_id | Partition key. All comments for a stream live together. |
comment_id | Sort key. A time-sortable unique ID (Snowflake-style), so sorting by ID is sorting by time. |
author_id, text | Put the display name and avatar URL in the pushed event so clients never look up profiles. |
video_offset_ms | Position in the broadcast when the comment was posted, for lining comments up on replay. |
status | Visible, removed or pending review, so moderation can retract a comment. |
A wide-column or key-value store such as Cassandra or DynamoDB fits this pattern: append within a partition, read the newest N, page backward. Use cursor pagination on comment_id; offsets get slow on long streams and repeat or skip items while new comments keep landing.
3. Choose the transport: polling, SSE or WebSockets
| Option | Fit for live comments |
|---|---|
| Short polling | Simple and cacheable, but wasteful and laggy. Useful as a fallback or a snapshot mode for huge streams. |
| Long polling | Works through strict proxies, but every batch costs a new request. |
| Server-Sent Events | Strong default. One HTTP response stays open, server to client only, with automatic reconnect and resume through event IDs. |
| WebSockets | Full-duplex connection opened with an HTTP Upgrade (RFC 6455). Best when clients also send frequent events or the app already runs a socket gateway. |
The read path is one-way, so SSE for delivery plus HTTP POST for writes is a clean answer, and it is the approach LinkedIn described for live-video likes: an EventSource connection per viewer. SSE details worth knowing:
- If the server tags each event with an
id, the browser's automatic reconnect carries aLast-Event-IDheader so the server can resume from that point. - Without HTTP/2, browsers allow only six open SSE connections per browser per domain, according to MDN. Over HTTP/2 the streams share one connection, with a negotiated limit that defaults to 100.
- Proxies that buffer responses will stall the stream, so turn buffering off on that route.
With WebSockets you write reconnect and resume logic yourself. Either way, the connection layer resembles the gateway in a chat app design; the difference is that chat routes each message to a few recipients, while live comments broadcast to everyone watching.
4. The fan-out topology
Viewers of one video are spread across many connection servers (gateways). Every gateway holding at least one viewer of that video needs a copy of each comment, and no other gateway should get one. Walk through three options:
- Broadcast to every gateway. Simple, but each gateway processes the whole platform's comment volume and drops most of it. It stops scaling quickly.
- Pub/sub channel per video. Each gateway keeps an in-memory map of
videoId → local connections, subscribes tocomments:Vwhen its first viewer of video V arrives, and unsubscribes when the last one leaves. A Layer 7 load balancer that routes viewers by a consistent hash of the video ID keeps each audience on fewer gateways. - Dispatcher with a subscription table. Gateways register "gateway G has viewers of video V" in a shared key-value store, and a dispatcher tier forwards each comment only to those gateways. This is the shape LinkedIn described, including multi-region delivery: a dispatcher forwards each event to peer dispatchers in other data centers, which deliver it only if they have local subscribers.
The write path, end to end: the comment service authenticates, rate-limits, runs fast moderation checks and assigns a comment_id; persists the comment and appends it to a short recent-comments buffer (for example, a capped list in Redis); publishes it to the video's channel or dispatcher; and each subscribed gateway pushes it to local viewers.
Which pub/sub? Redis Pub/Sub is a common pick because channels are cheap and dynamic. Its documentation is explicit that delivery is at-most-once: a disconnected subscriber loses the message. Since Redis 7.0, sharded Pub/Sub (SSUBSCRIBE, SPUBLISH) keeps each message within one cluster shard instead of sending it to every node. At-most-once is acceptable because the database, not the push channel, is the source of truth. Kafka fits the durable side (moderation, analytics, persistence) better than one topic per short-lived stream; Gupta made a similar point, noting a Kafka-based push path would add guarantees at the cost of speed and frontend scalability.
5. Hot streams: one video, a huge audience
Facebook's 2015 engineering post on Live said the system had to handle the potential of more than a million people watching one broadcast at the same time. At that size the fan-out cost explodes, and nobody can read thousands of comments a second anyway.
- Two-level fan-out. Pub/sub sends one copy per gateway; each gateway fans out to its local connections in memory, so the cost spreads across many machines.
- Batch on a timer. Flush one batch per stream every few hundred milliseconds instead of one push per comment. The delay goes unnoticed in a fast-scrolling feed.
- Sample and rank. Above a comment-rate threshold, deliver a subset. Favor people the viewer follows, the broadcaster and accounts in good standing, and always show viewers their own comment immediately (optimistic UI).
- Switch to snapshot mode. Past a threshold, some designs stop pushing: a worker writes the latest 100 to 200 comments to a CDN-cached object about once a second and clients poll it. Use hysteresis so a stream near the boundary does not flap between modes.
- Aggregate reactions. Push likes and emoji as periodic counts, not one event per tap.
- Protect the join spike. Serve initial history from the recent buffer or a short-lived cache and coalesce identical requests. Facebook's Live video edge used request coalescing against the same kind of stampede on video segments.
State the trade-off plainly: on a hot stream you trade completeness for liveness. Viewers get a fast, representative feed, and the full history stays available through the paginated API.
6. Ordering, duplicates and reconnects
- Order per stream, not globally. Clients insert comments by their time-sortable ID and hold a short reorder buffer, because comments routed through different gateways or regions can arrive slightly out of order.
- Durability lives in the database. In his talk, Gupta said LinkedIn's live-likes system did not aim for complete delivery guarantees; it sent events to clients fire-and-forget and favored speed. A lost push is still recoverable from history.
- Resume after a drop. Set each SSE event's
idto thecomment_id. On reconnect, any gateway can replay newer comments from the shared recent buffer; cap replay at a few minutes and send the client to the history API for anything older. - Deduplicate on the client by
comment_id, since replay and live delivery overlap and the author's optimistic copy comes back over the stream. - Video lag versus comment lag. Video reaches viewers seconds behind the broadcaster (Facebook's 2015 post described targeting two to three seconds), while comments can arrive faster.
video_offset_mslets replays line comments up with the moment they refer to.
7. Moderation without killing latency
| Stage | Checks | Latency cost |
|---|---|---|
| Synchronous, in the write path | Auth, per-user and per-stream rate limits, length, blocked terms, users the broadcaster banned, slow mode, followers-only mode | Milliseconds, using cached data |
| Asynchronous, after publish | ML classifiers for spam, harassment and links; user reports; account trust signals | None for the viewer |
| Retraction | Publish a comment_removed event on the same channel; clients drop it; storage status changes | One small event |
Rate limiting doubles as spam control and load control; the patterns in our rate limiter walkthrough apply directly. Name the trade-off: holding every comment for a classifier is safer but slower, while publish-then-retract stays live but lets a bad comment show briefly. A middle path is risk-based routing: trusted accounts publish immediately, new or flagged accounts wait for review.
8. Back-pressure and failure handling
- Slow clients. Give each connection a bounded send queue. When a phone on a weak network falls behind, drop or coalesce older comments, and disconnect it if it stays behind. One slow consumer must never block a gateway's broadcast loop.
- Overloaded gateways. Lower the sampling rate for the hottest streams on that node before refusing connections.
- Write bursts. Put a durable queue between the comment service and slower consumers (moderation, analytics, indexing) so a spike does not stall publishing.
- Heartbeats. Send keepalives (an SSE comment line, or WebSocket ping and pong frames) so idle connections survive proxies and dead clients get cleaned up.
- Reconnect storms. When a gateway dies or redeploys, its clients reconnect at once. Use exponential backoff with jitter, drain gateways gradually during deploys, and keep resume-from-ID cheap.
Telling followers that someone just went live is a separate fan-out; treat it as a notification system. Ingesting and transcoding the video belongs to the video pipeline, not this design.
Trade-offs to state, and how to pace the answer
| Decision | Default | Switch when |
|---|---|---|
| Transport | SSE for delivery, HTTP POST for writes | Clients send frequent events too: use WebSockets |
| Fan-out | Pub/sub channel per video | You need tighter routing or multi-region delivery: add a dispatcher |
| Hot streams | Push every comment | Audience or comment rate passes a threshold: batch, sample or use CDN snapshots |
| Moderation | Fast sync checks, async classifiers | High-risk streams or new accounts: hold for review |
For a 45-minute round, a workable split is about five minutes on requirements and numbers, five on the API and data model, fifteen on the high-level design and fan-out, and the rest on the deep dives the interviewer picks. Interviewers steer differently, so follow their lead.
The fastest way to find gaps is to explain this design out loud against a clock. A free AI mock interview in CoPilot Interview lets you include system design in the question mix, answer aloud, and get a score on each answer with what worked and what to improve.
Practice this design out loud
Paste a job description and your résumé, add system design to the question mix, and answer out loud. Each answer gets a score with what worked and what to improve, and the free plan gives you 15 minutes a day with no credit card.
Try a free AI mock interviewFAQ
Should I use WebSockets or Server-Sent Events for live comments?
SSE is a strong default because comment delivery is one-way, writes can be ordinary HTTP POST requests, and the browser reconnects and resumes with the Last-Event-ID header for you. Choose WebSockets if clients also send frequent events over the same connection or the product already runs a WebSocket gateway.
How do you handle a live video with millions of viewers?
Fan out in two levels, one copy per gateway server and then to local connections in memory, and batch comments on a short timer. Past a threshold, show a ranked sample instead of every comment, or serve a snapshot of recent comments from a CDN that clients poll about once a second.
Do live comments need to be in perfect order?
No. Viewers need a sensible order within one stream, not a global order, so time-sortable comment IDs plus a short client-side reorder buffer are usually enough.
What happens when a viewer's connection drops?
The client reconnects with backoff and jitter and sends the last comment ID it saw, which SSE does automatically when the server sets event IDs. The server replays newer comments from a short recent buffer, and the client drops duplicates by comment ID.
How do you moderate live comments without adding latency?
Run cheap checks such as rate limits, blocked terms and banned users synchronously, and run heavier classifiers after the comment is published. If a comment is flagged later, publish a removal event on the same channel so every client drops it.