SysDesignPrep.com
System design interview question

Design Twitch

Ingest 100 k live broadcasts, transcode them in real time, and deliver each about three seconds behind live, to three viewers or 500 k.

Last updated 2026-10-26. Difficulty: hard. Patterns: live-video, low-latency, transcoding, cdn, request-coalescing. Reported at Amazon, Google, Meta, TikTok, Netflix, Microsoft, Discord.

Walk through a strong candidate's answer, turn by turn.

The interviewer asks, the candidate answers and draws, and you press Next. Skip to any stage or key moment, and ask the AI Mentor anything along the way.

Or sit it in the style of Amazon · Google · Meta · TikTok, run as that company’s round is reported to go.

The short answer

How to design Twitch in a system design interview, in five steps:

  1. Scope it. Go live from broadcasting software, watch live, a few seconds behind, quality that suits the connection, any audience size, survive broadcaster hiccups, record every broadcast, live directory and viewer counts.
  2. Set the targets. Latency: ~3 s glass to glass · p95 < 5 s; Scale: 100 k live channels · 5 M viewers; Egress: ~15 Tbit/s at peak; Smooth playback: rebuffer < 1 % · join < 2 s.
  3. Do the numbers. Ingest bandwidth: ~600 Gbit/s; Peak egress: ~15 Tbit/s; The biggest channel alone: ~1.5 Tbit/s.
  4. Draw the design. Ingest PoP, Stream control, Transcode scheduler, Live transcoder, Packager / origin, Origin shield, CDN edge, Playback API, Live state.
  5. Go deep where it is hard.
    • Where the seconds go → LL-HLS: 2 s segments, 0.5 s parts, blocking reload and preload hints.
    • A million requests for a part that does not exist yet → Two coalescing cache tiers that stream in-flight objects and use blocking playlist URLs as cache keys.
    • Which channels get a full ladder → Ladder decided per channel from expected audience, upgraded live.
    • Ingest at the edge, transcode at the origin → Thin ingest PoPs forwarding over the backbone to a few origin sites with pooled transcoders.
    • Disconnects, reconnects and failover → A session with a grace period, epoch fencing and a slate from the transcoder.
    • Recording and VOD → Write segments to object storage as they are produced, with a growing VOD playlist.

Functional requirements

  • Go live from broadcasting software. OBS, a console or a phone app pushes one high-quality stream authenticated by the channel’s secret stream key: RTMP for most encoders, SRT or WebRTC (WHIP) where supported. The channel is watchable within a few seconds of the first frame.
  • Watch live, a few seconds behind. About 3 seconds glass to glass by default, so chat and the stream feel like one conversation. A normal-latency mode (around 10 s) for players that cannot do low latency and viewers on poor networks.
  • Quality that suits the connection. A ladder from 160p up to the source, chosen automatically by the player part by part, plus a manual quality menu and audio-only.
  • Any audience size. Most channels have a handful of viewers; a few have hundreds of thousands, and a raid can move 50 k viewers onto a small channel in seconds. The same system serves all of them.
  • Survive broadcaster hiccups. A dropped connection that comes back within 30 seconds continues the same broadcast: viewers see a short “reconnecting” slate rather than an offline page, and the broadcast stays one recording.
  • Record every broadcast. Each broadcast becomes a VOD the moment it ends, and is rewindable while live. It is kept for a retention period set by the account’s tier.
  • Live directory and viewer counts. Which channels are live, at which qualities, and roughly how many people are watching, refreshed every 10 to 30 seconds.
  • Out of scope. Chat (the separate Live Comments question), monetisation and ad insertion, recommendations and directory ranking, clip editing, moderation, and DRM.

Non-functional requirements

  • Latency (~3 s glass to glass · p95 < 5 s). This is the requirement that drives the hardest trade-off. The player buffer is the largest share of the delay, so every second removed is paid for in rebuffering risk, request rate and what the CDN must do.
  • Scale (100 k live channels · 5 M viewers). At peak, with one channel at 500 k. The audience is heavily skewed: the top 10 k channels hold about 90 % of viewers, and the median channel has two or three.
  • Egress (~15 Tbit/s at peak). Every viewer pulls their own copy, 25 times the ingest volume. Nothing can be pre-positioned, because each byte exists only a second before it is watched.
  • Smooth playback (rebuffer < 1 % · join < 2 s). With only about 1.5 s of buffer, the player must step down quality early; joining must not wait for a whole segment.
  • Broadcast reliability (no broadcast ended by our failure). An ingest PoP or transcoder failure may cost viewers a few seconds but must never end the broadcast. Streamers lose part of their audience every time they go offline.
  • Recording durability (no lost broadcast). Segments reach object storage within seconds of being produced, so a VOD survives the failure of any live component.
  • Cost (egress + transcoding dominate). Egress scales with viewers, transcoding with channels. Transcoding every channel the same way spends most of the compute on channels almost nobody is watching.

Back-of-envelope estimates

  • Ingest bandwidth: ~600 Gbit/s. 100 k live channels × ~6 Mbit/s source (1080p60 is the common upload) = 600 Gbit/s into the ingest PoPs. Large, but small next to what goes out.
  • Peak egress: ~15 Tbit/s. 5 M concurrent viewers × ~3 Mbit/s average delivered bitrate (a mix of source, 720p60 and mobile renditions) = 15 Tbit/s, 25× the ingest. One viewer-hour is ~1.35 GB. Egress is the largest bill in the system.
  • The biggest channel alone: ~1.5 Tbit/s. 500 k viewers × 3 Mbit/s = 1.5 Tbit/s, a tenth of the platform’s egress on one stream. Over ~100 edge PoPs that is ~5 k viewers and ~15 Gbit/s per PoP for a single set of objects, more than one cache server should carry.
  • Edge requests per second: ~20 M/s. A low-latency viewer makes two requests per 0.5 s part, a blocking playlist reload and the part itself, so 4 req/s each. 5 M × 4 = 20 M req/s, half of them for playlists: the playlist is requested as often as the video and is the only object that changes.
  • Origin fetches for the 500 k channel: ~120 part fetches/s. Without coalescing: 500 k viewers × 2 parts/s = 1 M req/s at the origin. With edges and ~10 regional shields each collapsing to one upstream fetch per object: 10 shields × 6 renditions × 2 parts/s = 120/s, and about the same again for playlists. Audience size no longer reaches the origin.
  • Transcode fleet: ~70 k vCPU, not 600 k. A full ladder (720p60, 480p, 360p, 160p, audio) from a 1080p60 source costs ~6 vCPU in software. For every channel: 100 k × 6 = 600 k vCPU. Ladder only for the top 10 k channels and repackage the source for the rest at ~0.1 vCPU: 60 k + 9 k ≈ 70 k vCPU, about 9× less, while still covering ~90 % of viewers.
  • Recording volume: ~4 PB/day. About 60 k channels live on average across the day × 24 h = 1.44 M broadcast-hours. Source at 6 Mbit/s is 2.7 GB an hour: 1.44 M × 2.7 GB ≈ 3.9 PB/day, plus ~10 % for the ladders of transcoded channels. A 7-day default retention holds ~30 PB, which is why retention is a product decision.

Components

  • Broadcaster (OBS · RTMP / SRT / WHIP): The streamer’s encoder (OBS, a console, a phone app) sends one stream, typically 1080p60 H.264 at ~6 Mbit/s with a keyframe every 2 seconds, authenticated by the channel’s stream key. It reconnects by itself after a drop, which is what makes a reconnect grace period worth having.
  • Ingest PoP (nearest of many · thin): Terminates RTMP, SRT or WebRTC close to the broadcaster, so the lossy, uncontrolled part of the path is short, then forwards the stream over our backbone to the origin site chosen for it. Deliberately thin: check the key, buffer a few seconds, forward. Broadcasters reach the nearest healthy PoP through DNS steering.
  • Stream control (keys · sessions · epochs): Owns each broadcast session: validates the stream key, creates the broadcast id, tracks live, reconnecting and ended, and runs the reconnect grace timer. Every accepted connection raises the session’s epoch, so a stale connection or transcoder can be fenced out.
  • Transcode scheduler (ladder per channel): Places each broadcast on a transcoder slot in an origin site with spare capacity and decides its ladder: full for partners and channels with an audience, passthrough for the rest. Upgrades a channel mid-stream when its viewer count crosses a threshold, and gives the biggest channels a second, aligned transcoder.
  • Live transcoder (real-time ABR ladder): Decodes the source once and encodes every rendition in one process, so keyframes and timestamps line up across the ladder and a player can switch at any segment boundary. Emits CMAF chunks as frames are encoded. When its input stops, it encodes a slate for the grace period so playlists keep advancing.
  • Packager / origin (CMAF parts · LL-HLS): Groups chunks into 0.5 s parts and 2 s segments, keeps each rendition’s playlist current, and answers blocking playlist requests the instant the requested part exists. Holds the last few minutes in memory, writes each finished segment to the recording store, and never serves a viewer directly.
  • Origin shield (regional mid-tier): About ten regional caches between the edges and the origin. Collapses all edge misses for an object into one upstream fetch, holds blocking playlist requests, and for hot channels receives each new part by push. It is why origin load depends on renditions, not viewers.
  • CDN edge (coalescing · streams in-flight): PoPs near viewers. The first request for a part goes upstream and every later one waits on it, receiving bytes as they arrive rather than after the object completes. Blocking playlist URLs are cache keys that become valid exactly once, so even the playlist caches.
  • Viewer player (LL-HLS · ABR · catch-up): Web, mobile and TV players. Keeps about 1.5 s of buffer, chooses a rendition per part from buffer level and measured throughput, and plays slightly fast to win back latency after a stall. Sends a heartbeat every 30 seconds.
  • Playback API (join · token · CDN choice): Answers “play this channel”: is it live, which ladder, which CDN hostname, plus a short-lived signed token. The answer is per channel, not per viewer, so it is cached for a second or two and a 100 k-viewer join storm costs a handful of backend reads.
  • Live state (Redis · per channel): The hot, rebuildable view of what is live right now: broadcast id, origin site, ladder, latency mode, hot flag and viewer count per channel. Written by stream control, the scheduler and the viewer counter; read on every join.
  • Recording store (S3 · written as produced): Object storage holding every finished segment as {broadcast_id}/{rendition}/{seq}.m4s plus a VOD playlist that grows with them. Because it fills while the stream runs, the VOD exists the moment the broadcast ends. Lifecycle rules apply retention.
  • Channel DB (channels · broadcasts · VODs): Durable records: channels and hashed stream keys, broadcast sessions with their epochs, and VOD entries pointing at recordings. A row per go-live is a tiny write rate, so a relational store sharded by channel id is ample; the epoch update is a conditional write.
  • Stream events (Kafka · key = channel_id): Player heartbeats (channel, rendition, latency, rebuffering) and broadcast lifecycle events. Feeds viewer counts and quality monitoring, and the out-of-scope consumers such as go-live notifications and the directory.
  • Viewer counter (per channel · every 10 s): Counts distinct viewers per channel from heartbeats over a sliding window and writes the number to live state every 10 seconds. That one number drives the directory, ladder upgrades and the hot-channel flag, so it is an input to cost as well as to the UI.

User flows

  1. A broadcaster goes live. From the first RTMP packet to a playable low-latency stream in a few seconds. Authentication, placement and the ladder decision happen once, at connect time; after that the media path never waits on a database.
    1. The broadcaster’s encoder connects to the nearest ingest PoP and publishes with its stream key. DNS steering returns the closest healthy PoP, typically 10 to 40 ms away, so the lossy and uncontrolled part of the path is as short as possible. RTMP runs over TCP, so one lost packet stalls everything behind it; SRT retransmits over UDP within a fixed latency window and copes far better with a bad uplink.
    2. The ingest PoP asks stream control to validate the key and open a broadcast session. Control looks up the key by its hash and refuses a wrong or banned key before any media leaves the PoP. It either creates a new broadcast or, if the channel has one in its reconnect grace period, resumes it. Each accepted connection gets a higher epoch than the last, which later lets the transcoder ignore a stale connection that is still sending.
    3. Control asks the transcode scheduler for a slot and a ladder. The scheduler picks an origin site near the ingest PoP with spare transcode capacity and decides the ladder from what is known before anyone watches: partner status and the channel’s recent average audience, kept on the channel row and refreshed from analytics. A channel that averaged 4 viewers last week starts as passthrough; one that averaged 4 000 gets the full ladder from the first frame.
    4. The scheduler reserves the slot and the ingest PoP forwards the stream to it over the backbone. The forwarding hop runs on our own network, so it is low-loss and adds tens of milliseconds. The PoP keeps a few seconds of the stream in memory, so if the broadcast has to move to another transcoder it can replay from the last keyframe instead of losing frames.
    5. The transcoder encodes the ladder, or only repackages the source, and emits CMAF chunks. One process decodes once and encodes every rendition, forcing a keyframe at the same timestamp in all of them every 2 seconds, so segment N of 480p and segment N of 720p cover the same instant. Passthrough encodes nothing: it cuts the source at the broadcaster’s own keyframes, which is why platforms ask broadcasters for a 2-second keyframe interval.
    6. The origin assembles parts and segments and updates each rendition’s playlist. A part is announced as soon as it is complete, every 0.5 s, and a segment every 2 s. The playlist also carries a preload hint naming the next part, so players can request it before it exists. The origin keeps the last few minutes in memory and serves only shields, never viewers.
    7. Control marks the channel live and publishes a stream-started event. The channel shows as live only once its first segment is on the origin, so a viewer who clicks at once gets a playable stream rather than an empty playlist. Go-live notifications, the directory and search consume the event; none of them is on the media path.
  2. A viewer joins and watches. The read path. One cached API call, then plain HTTP to the nearest edge, where blocking playlist reloads and coalesced part requests keep each viewer about three seconds behind live without the origin ever seeing them.
    1. The player asks the playback API for the channel. One read of live state answers everything: live or not, broadcast id, ladder, latency mode and which CDN hostname to use. The answer is the same for every viewer of the channel, so it is cached for a second or two; the only per-viewer part, the token, is signed rather than looked up.
    2. The player loads the master playlist and picks a starting rendition. The master playlist lists the ladder with bandwidth, resolution and frame rate. The player starts a rung or two below what it guesses the connection can sustain, because with 1.5 s of buffer a wrong first guess stalls almost immediately.
    3. The player asks for the playlist that will contain the next part, and the request waits until that part exists. This is LL-HLS blocking playlist reload: instead of polling, the player names the part it wants and the request is held. Every viewer of the rendition asks with the same URL, so each edge sends one request upstream and each shield one to the origin, and all of them are answered together the instant the part is published.
    4. The player fetches each part from the edge, which fills it once from the shield. With a preload hint the player requests the part before it is finished, and the edge and shield pass bytes on as the origin produces them, so the part arrives as it is encoded rather than half a second later. Parts and segments are immutable, so they are cached for a few minutes for late joiners and rewind.
    5. The player keeps about 1.5 s of buffer, adapts the rendition part by part, and catches up after a stall. With so little buffer, it steps down as soon as the buffer falls below about 1 s and steps up only after several parts arrive well ahead of need. Because parts stream at the speed they are produced, download time no longer measures bandwidth, so throughput is measured only over bursts where bytes were already waiting. After a stall it plays at 1.05 to 1.1× until it is back at its latency target.
    6. Every 30 seconds the player sends a heartbeat, which the viewer counter aggregates per channel. The heartbeat carries rendition, measured latency and rebuffering, so one stream feeds both viewer counts and quality dashboards. The counter writes a count per channel every 10 seconds; the directory, the ladder decisions and the hot-channel flag all read that one number.
  3. One channel climbs to 500 k viewers. The scale-breaking case. Live content cannot be pre-positioned, so the defence is to stop audience size from reaching anything that does not scale with it: coalescing in two cache tiers, push to the shields, redundant transcoders and a cached join path.
    1. The viewer counter sees the channel pass 20 k viewers and the scheduler marks it hot. Scheduled events such as an esports final are marked hot hours ahead by an operator; organic growth is caught by the count. Hot is sticky for the rest of the broadcast, so a dip in viewers does not undo the preparation.
    2. The scheduler starts a second, aligned transcoder for the channel in another failure domain. Both encoders cut segments at the same source timestamps and number them the same way, so the origin can take either one’s output for any segment and losing a transcoder costs nothing visible. This 1+1 redundancy is too expensive for 100 k channels and cheap for the few hundred that carry a large share of the audience.
    3. The origin pushes each new part to every regional shield as it is produced. For a channel watched in every region, waiting for each shield to ask adds a round trip to every part, and push removes it. For small channels pull is cheaper, because most of them are watched from one or two regions. Either way the origin sees about 120 part requests a second for this channel, the same as at 5 k viewers.
    4. Edge servers collapse their viewers’ requests into one upstream fetch per part per rendition. At ~5 k viewers per PoP, each part is wanted by thousands of clients within about 100 ms; the edge answers them all from one fetch, streaming bytes as they arrive. At ~15 Gbit/s per PoP for one channel, the PoP replicates the channel’s objects across several cache servers instead of hashing each URL to just one.
    5. The playback API absorbs the join storm and moves a share of viewers to a second CDN if a region runs hot. A hundred thousand viewers joining in a minute are ~1 700 requests a second for one identical answer, served from the API’s one-second cache. For the largest events the playback API hands another CDN’s hostname to a percentage of viewers in regions near capacity; the objects are identical, only the hostname differs.
  4. The broadcaster drops and reconnects. The failure viewers notice most. A broken uplink must look like a short pause, not an ended stream, and a connection that comes back, even through another PoP, must continue the same broadcast without two inputs fighting over it.
    1. The broadcaster’s connection drops and the ingest PoP reports it to stream control. Control moves the session to “reconnecting” and starts a 30-second grace timer instead of ending the broadcast. A TCP connection can die silently, so the PoP treats 5 seconds without media as a drop rather than waiting for the socket to close.
    2. The transcoder fills the gap with a slate so the playlists keep advancing. After a second without input, the transcoder encodes a “reconnecting” image and silence into every rendition at continuous timestamps, so segment numbers carry on. Players keep playing the slate instead of stalling, retrying and showing “offline”, and their buffers and positions survive the gap.
    3. The encoder reconnects, possibly through a different PoP, and control resumes the same broadcast with a new epoch. Control finds an open session for the channel inside its grace period and resumes it rather than creating a new broadcast, so viewers, the VOD and the viewer count all continue. The epoch moves from 1 to 2 with a conditional update, so two racing connections cannot both win.
    4. The new PoP forwards to the same transcoder, which accepts only the new epoch and switches back from the slate. If the old connection is half-open and still trickling packets, they carry epoch 1 and are dropped, so two inputs can never interleave. The switch happens at the new input’s first keyframe and is marked as a discontinuity, because timestamps, and even resolution, may have changed.
    5. Players cross the discontinuity without reloading. A player reaching the discontinuity tag resets its decoder timeline and carries on; the viewer sees the slate end and the stream resume. Without the slate, many players would have given up after a few empty playlist reloads and shown an error.
    6. If the transcoder itself dies, the scheduler restarts the broadcast on a new slot that continues the segment numbers. The scheduler notices the missed heartbeat within 2 to 3 seconds, starts a new slot with the last sequence number the origin published, and the ingest PoP replays its buffered seconds from the last keyframe. Viewers of a single-transcoder channel see a short stall or a slate; hot channels already run a second transcoder and see nothing.
  5. The stream ends and becomes a VOD. The background path. The recording is written segment by segment while the stream is live, so ending a broadcast means closing playlists, not processing hours of video.
    1. While live, the origin writes every finished segment of every rendition to the recording store. Each 2 s segment is uploaded as it completes, under a key built from the broadcast, rendition and sequence number, so a retried upload overwrites the same object. A VOD playlist listing them is rewritten every few segments, which also lets viewers rewind further than the live window.
    2. The broadcaster stops; once the grace period expires, control ends the session and the scheduler releases the slot. An explicit stop from the encoder ends the broadcast at once; a drop ends it after the 30-second grace period. The transcoder flushes its last partial segment before it exits, so the final seconds are not lost.
    3. The origin closes every live playlist and finalises the VOD playlist. Appending EXT-X-ENDLIST tells live players the stream is over, so they stop reloading. The VOD playlist is the same list of segments with an end tag and the slate gaps marked; nothing is re-encoded, so it is watchable straight away.
    4. Control records the VOD, clears live state and publishes a stream-ended event. The VOD row points at the recording prefix and carries the expiry date from the account’s retention tier. Clearing live state takes the channel out of the directory; the event lets the viewer counter close its window and analytics total up the broadcast.
    5. VOD viewers play through the same CDN, with shields filling from the recording store. A VOD is ordinary on-demand video: immutable segments, long cache lifetimes, and a 30-second player buffer instead of 1.5. Most VOD views come in the first day, from viewers who missed the stream, so recent recordings are hot and the rest is a long tail.
    6. Lifecycle rules expire recordings at the end of their retention period. At about 4 PB a day, retention is the storage bill: 7 days by default, longer for partners, and no expiry for highlights the streamer keeps. A highlight is a playlist over a subset of segments, copied to a prefix that does not expire when it is created.

Deep dives

Where the seconds go

Standard HLS runs 20 seconds behind live. How do you get to 3 without viewers stalling?

Glass-to-glass latency is the sum of capture and encoding on the broadcaster’s machine, the uplink, transcoding, packaging, CDN delivery and, by far the largest term, the player’s buffer. Classic HLS starts a player three target durations from the end of the playlist, so 6-second segments mean ~18 s of buffer plus waiting for each segment to be finished: 20 to 30 seconds. Chat makes that delay visible, because viewers react to a moment the streamer left long ago.

The buffer exists to absorb jitter, and how small it can be depends on how finely media is delivered. The insight is to separate the unit of compression (the segment and its keyframe, which should stay around 2 seconds) from the unit of delivery (a part of a fraction of a second), so the player can sit close to live without making encoding less efficient.

  • Standard HLS or DASH with 6 s segments situational: as the fallback for players without low-latency support, and for broadcasts where interaction does not matter
  • Shorter plain segments (1 s) rejected
  • LL-HLS: 2 s segments, 0.5 s parts, blocking reload and preload hints chosen
  • WebRTC to every viewer situational: for the few people who must interact in real time, such as guests in a co-stream, with LL-HLS for the audience

The answer: LL-HLS over CMAF: a keyframe every 2 s, 2 s segments, each published as four 0.5 s parts. The playlist announces parts as they complete, plus a preload hint for the next one, and players use blocking playlist reload (_HLS_msn and _HLS_part) so they learn of a part the moment it exists instead of polling. The budget at the default target is about 0.3 s for the broadcaster’s encoder, 0.15 s of uplink, 0.05 s of backbone, 0.3 s of real-time transcoding, 0.5 s to complete a part, 0.1 s through shield and edge, and 1.5 s of player buffer (three parts): roughly 2.9 s. The buffer is the only large term and the only one worth trading, so it is the knob: a viewer on a jittery network gets a larger hold-back automatically rather than stalls. A normal-latency mode with plain 2 s segments and about 6 s of buffer remains as the fallback.

Why keep 2-second segments at all if parts do the delivery?

Because the segment is where the keyframe is, and keyframes are expensive: halving the GOP to 1 s costs roughly 10 to 20 % more bits for the same picture. Segments are also what the recording, the VOD and normal-latency players use. Parts make delivery fine-grained while compression and storage stay coarse.

Your target is 3 s, but a viewer on hotel Wi-Fi keeps stalling. What do you do?

Let that player grow its own hold-back: after a stall it aims for 4 or 5 seconds instead of 3, then creeps back while its network behaves. Latency is a per-viewer setting, not a global one. If it still stalls, ABR drops a rung, and the viewer can choose normal latency, which most never need.

How does a viewer joining mid-segment start quickly?

Segments begin with keyframes and parts that start with one are flagged INDEPENDENT in the playlist, so the player starts from the latest independent part rather than waiting for the next segment. It downloads from there at full speed, then plays slightly fast until it reaches its target latency. Join time is the playback API call, the master playlist and the first media, comfortably under 2 s.

Why not chunked transfer of whole segments instead of parts?

It works and needs fewer requests: one per 2 s segment, streamed as it is produced, which is the approach Periscope published as LHLS and low-latency DASH uses. The drawbacks are that native Apple players require LL-HLS, and an open-ended download makes bandwidth estimation harder still. Both use the same CMAF chunks, so one packager can serve LL-HLS to Apple devices and chunked DASH to players that prefer it.

How would you measure latency in production?

Stamp wall-clock time into the stream at ingest (the program date-time tag in the playlist, or a timed metadata box per segment) and have the player report now minus that stamp in every heartbeat. That gives ingest-to-glass latency per viewer, region, CDN and rendition, and its p95 is the SLO. The broadcaster’s own encoding delay, about 0.3 s, is outside what we can measure and is estimated separately.

A million requests for a part that does not exist yet

A new part appears every half second and every viewer wants it at once. What reaches the origin, and how do you cache a playlist that changes twice a second?

On-demand video can be pushed to caches overnight; live video cannot, because each part exists for only a second or so before it is watched. Every 0.5 s, for each rendition of each channel, a new object appears and all of that rendition’s viewers request it within about 100 ms. For the 500 k channel that is a million requests a second for objects that did not exist a moment earlier, and a naive pull-through cache forwards every one of those misses.

The playlist is worse: it changes with every part, is requested as often as the media, and a stale copy makes players fall behind or ask for parts that do not exist. What rescues both is that all viewers of a rendition want exactly the same bytes at exactly the same time, so each cache tier’s job is to turn N identical requests into one, including requests for objects still being written.

  • Pull-through CDN with short TTLs rejected
  • Two coalescing cache tiers that stream in-flight objects and use blocking playlist URLs as cache keys chosen
  • Origin pushes every part to every edge situational: push to the ten shields only, and only for hot channels; edges keep pulling
  • Peer-to-peer delivery between viewers situational: as an egress lever for a few record-breaking events, never the primary path

The answer: Edges plus about ten regional shields, all coalescing. The first request for an object goes upstream; later ones attach to it and receive bytes as they arrive, so 5 000 viewers in a PoP cost one fetch and nobody waits for a part to finish before getting its first bytes. Parts and segments are immutable and cached for five minutes, which covers late joiners and rewind. Blocking playlist requests carry _HLS_msn and _HLS_part in the URL and the cache key includes them, so “the playlist that contains part 1043.1” is an object that becomes valid once and never changes: it is coalesced and cached like media, and the origin holds one waiting request per shield per rendition. The only mutable object left is the plain playlist for normal-latency players, cached for half a segment. For hot channels the origin pushes parts to the shields and edges spread the channel across several servers per PoP. Origin load for the 500 k channel is about 120 part fetches a second, the same as at 5 k viewers.

The first request for a part reaches the edge 50 ms before the part exists. What happens?

With a preload hint that is the normal case: the edge forwards it, the shield forwards it, and the origin holds it open until the part starts, then streams it. Every request arriving meanwhile attaches to the same pending fetch. A request for something the playlist has not hinted, such as a part two ahead, gets a 404 that must not be cached for more than a moment, or it poisons the edge for everyone.

One edge server holds the hot part and 5 000 viewers in its PoP want it. Is that a problem?

At 3 Mbit/s each that is 15 Gbit/s of one object from one server, near or beyond what one machine’s network card should carry. CDNs normally hash each URL to one server per PoP for cache efficiency, so a hot channel needs the opposite: copy its objects to several servers and spread viewers across them. The trigger is the same hot flag that turns on shield push.

Why not let the edge cache the plain playlist for one second and skip blocking reload?

For normal-latency players we do exactly that. For low latency, a one-second-old playlist is two parts stale, so players would sit at least a second further from live and could not know about the part being produced. Blocking reload removes both the polling and the staleness: the response arrives the instant the part does, and it is the same response for everyone.

A shield fails during the 500 k-viewer final. What do viewers see?

Its edges retry against the next-nearest shield, which is already fetching the same parts for its own region, so the origin sees nothing new and that shield gains one more set of edge fetches. Viewers in the region may get one part late; with 1.5 s of buffer that is usually absorbed, occasionally a short stall. Shield health is checked in seconds, because live has no room for long timeouts.

How do you know coalescing is working?

Track origin requests per channel per rendition per second against the number of shields; the line should stay flat as the audience grows. At the edge, track the collapsed-request ratio and time to first byte for parts, which should be close to the part’s publish time plus one network hop. A rising origin request rate for one channel usually means a cache key is varying, often a per-viewer token that leaked into the URL.

Which channels get a full ladder

Why not transcode every channel into every rendition?

A real-time ladder from a 1080p60 source (720p60, 480p, 360p, 160p and audio) costs about 6 vCPU for as long as the channel is live, whether one person is watching or a million. Across 100 k channels that is 600 k vCPU, most of it spent on the 90 k channels that share 10 % of the audience.

The two big costs behave differently: transcoding is per channel, egress is per viewer. With illustrative unit costs of $0.02 per vCPU-hour and $0.003 per GB delivered, a ladder costs about $0.12 an hour, the egress of roughly 30 viewer-hours. For a channel with 3 viewers the ladder costs more than its whole egress bill; for one with 50 k it is a rounding error, and it saves money, because a viewer on 480p pulls 1.5 Mbit/s instead of 6.

  • Full ladder for every channel rejected
  • Source passthrough for everyone rejected
  • Ladder decided per channel from expected audience, upgraded live chosen
  • The broadcaster encodes the ladder and uploads every rendition situational: for broadcasters with modern GPUs and fast uplinks; Twitch’s Enhanced Broadcasting, in which OBS encodes several renditions on the streamer’s own GPU, is this model

The answer: Three tiers chosen by the scheduler at go-live from partner status and the channel’s recent average audience: full (six renditions including the source), lite (source plus 360p, about 1 vCPU) when spare capacity allows, and passthrough (the source repackaged, about 0.1 vCPU). A passthrough channel is upgraded mid-stream when its count stays above about 30 viewers for a minute, roughly where the ladder pays for itself in egress, or immediately when a raid toward it starts. Ladders are not removed mid-broadcast, because removing a rendition interrupts everyone watching it. Partners have reserved capacity, and everyone else gets ladders in order of audience until the pool is full, which matches how Twitch has long described its policy: quality options guaranteed for Partners and offered to other channels as capacity allows. Hardware encoders (GPUs or dedicated video chips) cut the cost of a ladder several times and lower the threshold, but do not remove it.

A streamer with 40 viewers is raided by a channel with 50 k. What happens in the next ten seconds?

The raid starts in our own product, so the scheduler hears about it before the viewers move and starts a full ladder for the target at once. Until the new renditions have their first segment, the master playlist offers the source only, so the first arrivals start on source and switch down as soon as lower renditions appear. The CDN needs nothing special: coalescing absorbs 50 k viewers on this channel the same way it does on any other.

How does a player that is already watching discover renditions added mid-stream?

The master playlist is normally read once, so the media playlist carries a small signal, a date-range tag, telling our player to reload the master. Third-party players that ignore it keep watching the source, which still works. New renditions start at a segment boundary with the same sequence numbers as the source, so moving to one is an ordinary rendition switch.

Egress is the bigger bill. What are its levers?

Send less: cap the rendition at the player’s size, since a 400-pixel embed never needs 1080p, and drop background tabs and unwatched players to a low rendition or audio only. Pay less per byte: committed rates with several CDNs and our own caches in the largest regions and ISPs. Use fewer bits for the same picture: HEVC or AV1 for the biggest channels where viewers’ devices decode it, because the encode cost is per channel and the saving is per viewer.

What happens at peak when the transcode pool is full?

Lite ladders are the first thing to go: running ones stay, but no new ones start. A partner or raid target that needs a slot is placed in another origin site with spare capacity, at a few tens of milliseconds more latency. Capacity is planned with about 20 % headroom over each site’s daily peak, so the last resort, removing a running ladder, should almost never happen.

Why decide at go-live rather than wait to see the audience?

Because a big channel’s viewers arrive within seconds of the go-live notification, and starting them on a 6 Mbit/s source loses mobile viewers in the first minute. A channel’s audience is stable from one broadcast to the next, so history is a good predictor. Waiting for the count is only for channels with no history, and the upgrade costs one segment boundary.

Ingest at the edge, transcode at the origin

Why forward the stream to a central transcode cluster instead of transcoding at the PoP that received it?

Broadcasters are everywhere, and their uplinks are the worst part of the path: home Wi-Fi, residential upload, sometimes mobile data. On a lossy TCP path, sustainable throughput falls as round-trip time grows, so the first hop should be as short as possible. That argues for many small ingest PoPs close to broadcasters.

Transcoding wants the opposite: a few large pools. Broadcasts start and stop at random, and one pool of 70 k vCPU absorbs that variance far better than a hundred pools of 700, which would strand capacity in the wrong places and make every PoP a transcode site to operate. The two concerns split cleanly, with our backbone in between.

  • Thin ingest PoPs forwarding over the backbone to a few origin sites with pooled transcoders chosen
  • Transcode at the ingest PoP situational: a region far from any origin site, or a large event with its own production setup
  • Broadcasters connect straight to the origin sites rejected
  • A managed cloud live-encoding service per channel situational: a small platform, or overflow for a one-off event

The answer: Many thin ingest PoPs, reached through DNS steering to the nearest healthy one, that terminate RTMP, SRT and WHIP, check the key with stream control, keep a few seconds of buffer and forward over the backbone to an origin site chosen by the scheduler. Sites are picked by proximity first and spare transcode capacity second, and a full site sheds new broadcasts to the next, so the fleet behaves like one pool. The PoP’s buffer lets a broadcast move to a new transcoder by replaying from the last keyframe. Twitch’s engineering blog has described this shape: ingest at edge PoPs, then each stream routed over its backbone to an origin data centre chosen by a service that tracks transcode capacity and network health.

An ingest PoP loses power. What happens to the broadcasters on it?

Their connections drop, their encoders reconnect after a few seconds, and DNS, which has already pulled the failed PoP, sends them to the next nearest one. Stream control resumes each broadcast within its grace period, so viewers see a few seconds of slate. Nothing about a broadcast lived only in the PoP, which is the point of keeping it thin.

Why offer SRT or WebRTC if RTMP works?

RTMP runs over TCP, so on a lossy uplink one lost packet stalls everything behind it and the encoder starts dropping frames. SRT retransmits over UDP within a fixed latency window, which survives far worse networks, and WHIP lets a browser go live with no software installed. All three become the same internal stream at the PoP, so nothing behind it cares.

How do you stop someone streaming with a stolen key?

Keys are stored hashed, can be revoked instantly, and resetting one ends the current broadcast. Only one connection per channel is accepted and the newest wins through the epoch, so the owner reconnecting cuts off a thief, and repeated takeovers from different networks are an alert. Limits on bitrate and resolution per channel stop a key from being used as free video hosting.

The backbone link from Europe to an origin site degrades. What do you do?

Stop placing new broadcasts from the affected PoPs on that site first. For existing ones, prefer waiting, because moving a live broadcast costs a discontinuity. If loss starts hurting viewers, move broadcasts one at a time to another site at segment boundaries, with the PoP’s buffer covering the switch.

Disconnects, reconnects and failover

The streamer’s Wi-Fi drops for eight seconds. How does that not end the broadcast, and how do you stop two connections fighting over it?

Uplinks drop all the time, and every time a channel goes offline its viewers scatter: players show “offline”, the directory removes the channel, and the VOD splits in two. A dropped TCP connection is not always closed, either: the old one can be half-open and still delivering buffered packets when the encoder’s new connection arrives, possibly at a different PoP.

Two things are needed. A broadcast must outlive its connection for a grace period, with something sensible shown to viewers in between. And exactly one connection must own the broadcast at any moment, decided by something stronger than whichever packet arrived last.

  • A session with a grace period, epoch fencing and a slate from the transcoder chosen
  • End the broadcast on disconnect and start a new one on reconnect rejected
  • Keep the session but let the playlist stall rejected
  • Two simultaneous uplinks from the broadcaster situational: esports and scheduled productions with redundant encoders and links

The answer: Stream control holds each broadcast as a session in live, reconnecting or ended. A drop (socket closed, or 5 seconds without media) moves it to reconnecting for 30 seconds, and the transcoder, after a second without input, encodes a slate into every rendition at continuous timestamps, so playlists keep advancing and players keep playing. Any new connection with the channel’s key resumes the session and raises its epoch with a conditional write in the channel DB; the transcoder accepts media only from the current epoch, so a half-open old connection is ignored. The switch from slate back to live is a discontinuity at the new input’s first keyframe. If the grace period expires, the broadcast ends normally and the VOD closes with the slate trimmed. Transcoder failure is handled from the other side: the scheduler restarts the broadcast on a new slot that continues the sequence numbers, and hot channels already run a second transcoder.

Why 30 seconds of grace? Why not five minutes?

Encoders reconnect within a few seconds and most real drops are short, so 30 seconds covers them. Longer means viewers stare at a slate while the streamer may have gone for good, and the transcode slot sits idle. It could be a channel setting, longer for streamers broadcasting from a phone on mobile data, whose connections drop more often.

Two encoders with the same key connect at the same moment. Who wins?

Both reach stream control, both attempt the conditional update from epoch 1 to 2, and exactly one succeeds; the other is refused at its PoP. If they connect one after the other, the later one wins with epoch 3 and the earlier is cut off. That is right when the owner reconnects after a half-open connection, and a visible alert when a key is being shared.

The stream comes back at 720p instead of 1080p. Does anything break?

The transcoder rebuilds its decoder at the discontinuity and stops producing renditions above the new source, removing them at the next master playlist reload rather than upscaling. Players treat a discontinuity as a reset point, so a change of resolution or even codec is fine. The VOD playlist keeps the discontinuity tag and plays through the change too.

How would you know reconnects are hurting viewers?

Count reconnects per broadcast-hour by ingest PoP and broadcaster ISP: a PoP whose rate rises is a network problem before streamers report it. On the viewer side, measure audience lost across a reconnect, the viewers watching just before the drop against those watching a minute after it resumes. That number is why the grace period and the slate exist.

Recording and VOD

Where does the recording come from, and why is the VOD available the moment the stream ends?

Every broadcast is recorded: about 1.44 M broadcast-hours and ~4 PB a day. If recording were a separate process, capturing the stream to a file and processing it after the broadcast, the VOD would lag the end of the stream by however long it takes to process hours of video, and a crash in the recorder would lose the broadcast.

But the live pipeline already produces exactly what a VOD needs: immutable, aligned 2-second segments in every rendition, numbered in order. The recording is a by-product, and the only work is to store those segments durably and keep a playlist that lists them.

  • Write segments to object storage as they are produced, with a growing VOD playlist chosen
  • Record the ingest stream to one file and transcode after the broadcast rejected
  • Keep the source only and transcode VOD renditions on demand situational: for VODs past their first week, when views have tailed off: drop the stored ladder and keep the source
  • Re-encode popular VODs offline at higher efficiency situational: for VODs with enough views to pay back the encode, using an on-demand pipeline like YouTube’s

The answer: The origin uploads each finished segment to object storage as {broadcast_id}/{rendition}/{seq}.m4s, keyed so a retry overwrites rather than duplicates, and rewrites a VOD playlist every few segments. Slate segments are recorded but marked, so the finished VOD can skip them. At the end the origin appends an end tag and stream control writes the VOD row, so viewers can start watching seconds after the stream ends, through the same CDN with long cache lifetimes. Objects are tagged at upload with the account’s retention class and lifecycle rules expire them: at ~4 PB a day, a 7-day default holds about 30 PB, so retention is the storage lever. Highlights are new playlists over a subset of segments, copied to a prefix that does not expire.

Uploads to object storage fail for two minutes. Is the recording lost?

No. The origin already keeps several minutes of segments in memory for live; it queues uploads and retries in order, and because keys are deterministic a retry that lands twice is harmless. Live playback is unaffected because it never reads from the recording store. If the outage outlasts the memory window, the origin spills to local disk, and the VOD playlist only ever lists segments known to be stored.

Millions of 2-second objects a day: is object count a problem?

It is billions of objects a year, which object stores handle, but per-request costs and listings become noticeable. Two remedies: after the first few days, when VOD views have tailed off, concatenate each rendition into one file and serve segments as byte ranges; and never list prefixes on the read path, since the playlist already names every object.

A streamer played copyrighted music. How do you handle it in the VOD without re-encoding hours of video?

Audio fingerprinting runs on the recorded audio after the stream, and matched spans are muted by rewriting only the affected segments with a silent audio track. Because segments are independent files, that is a few dozen new objects and a new playlist, not a new encode of the broadcast. The live stream is untouched.

How does rewinding ten minutes during a live stream work?

The live playlist covers only the last minute or two, so the player switches to the VOD playlist, which already lists everything up to a few segments ago. Those segments come through the CDN, filled from the recording store, with a normal on-demand buffer. Returning to live switches back to the low-latency playlist.

The theory behind it

  • Live video streaming: How live streams reach millions of viewers: ingest with RTMP, SRT or WebRTC, real-time transcoding, segmenting for HLS and DASH, low-latency HLS, CDN fan-out, WebRTC for interactive streams, latency trade-offs, live chat at scale, recording and reliability.
  • CDNs and edge computing: How a content delivery network works, push vs pull, cache keys and TTLs, what to put at the edge, edge functions and KV, and the pitfalls of caching dynamic content.
  • Video streaming: transcoding, HLS, DASH and adaptive bitrate: How online video works end to end: uploads, transcoding into a bitrate ladder, segmenting for HLS and DASH, adaptive bitrate in the player, CDN delivery, live versus on-demand, and the numbers behind it.

Related

Report an issue

Something wrong with this question? A number that does not add up, a step whose answer key looks off, a typo. It goes straight to the author with the page you are on.

Watch a strong candidate answer thisTurn by turn, with the design drawn as they talkWalk through →Mock interview