Design Video Streaming
Design a video streaming system (Netflix / YouTube). Covers the HLS / DASH adaptive bitrate protocols, the transcoding pipeline (source upload -> encode multiple resolutions -> manifest), CDN delivery with edge caching, and the client-side ABR controller that switches bitrates based on bandwidth. The deep dive walks through why we transcode to multiple resolutions (adaptive bitrate over flaky mobile), why segments are 10-second chunks (vs 1-second or 1-minute), and how the manifest file (HLS .m3u8 / DASH .mpd) ties everything together.
Foundational.
How it works
What are we designing? A video streaming service. A creator uploads a 4K source video; the system transcodes it to multiple resolutions (240p, 480p, 720p, 1080p, 4K) and bitrates, stores them in object storage behind a CDN, and serves them to viewers with adaptive bitrate — the player automatically switches to a lower resolution when the user's bandwidth drops, without rebuffering.
The defining challenges are (1) adaptive bitrate (ABR) — the same video must be available at multiple quality levels so the player can switch dynamically, (2) segmentation — the video is split into ~10-second chunks so the player can switch bitrates between chunks, and (3) CDN delivery at scale — millions of concurrent viewers watching different videos at different points; origin cannot serve this load directly.
Functional requirements.
- A creator uploads a source video (any format, up to 4K, up to 4 hours).
- The system transcodes it to multiple resolutions/bitrates (the 'ladder').
- A viewer requests a video; the player gets a manifest listing all bitrates and segments.
- The player fetches segments sequentially, switching bitrates based on measured bandwidth.
- The viewer can seek to any point in the video; playback resumes within ~1-2 s.
- The viewer can change quality manually (auto / 240p / 480p / 1080p / 4K).
Non-functional requirements.
- Time-to-first-frame (TTFF): < 2 s after the user clicks play.
- Rebuffer ratio: < 1% of playback time spent buffering (Netflix targets 0.5%).
- Origin egress: < 5% of total egress (CDN absorbs the rest).
- Upload-to-publish latency: < 30 min for a 10-min source video.
- Scale: 1B viewers, 500 hours of video uploaded per minute (YouTube scale), 1M concurrent streams per popular video.
Non-goals. No live streaming (different latency budget, different protocols like LL-HLS / WebRTC), no DRM (out of scope for v1), no transcoding-on-the-fly for viewers (pre-transcoded only).
Capacity estimation.
Storage. 500 hours uploaded per minute = 720K hours/day. Avg source 1 GB/hour (compressed 1080p) = 720 TB/day source = ~260 PB/year source alone. Transcode ladder produces 5 resolutions per source — total ~3x source = ~2 PB/day new content = ~780 PB/year. S3-class object storage with lifecycle to glacier after 30 days of no views.
Bandwidth (egress). 1B viewers x avg 30 min/day = 500M hours/day watched. Avg 3 Mbps per stream = 1.5 GB/hour = 750 PB/day egress = ~8.7 Tbps. With CDN absorbing 95%, origin egress = ~440 Gbps.
Transcoding CPU. 1 hour of 1080p video takes ~0.5 hour of single-CPU FFmpeg time at presets; ~0.1 hour with hardware acceleration. 720K hours/day source x 5 resolutions x 0.2 CPU-hours = ~720K CPU-hours/day. At 16 cores per instance = 45K instance-hours/day = ~1900 instances just for transcoding.
CDN cache. Catalog of 100M videos; hot 1% gets 80% of views. CDN caches the hot 1% (~1M videos x ~5GB = 5 PB at edge across 200 PoPs = 25 TB per PoP — fits).
APIs.
# Creator uploads (presigned multipart URL — same as Instagram / Dropbox)
POST /v1/videos/upload_session
-> { session_id, multipart_urls: [...] }
PUT <presigned S3 URL> part_bytes (per part)
POST /v1/videos/upload_session/:id/commit
{ title, description, creator_id }
-> { video_id, status: "processing" }
# Player fetches manifest (HLS .m3u8 or DASH .mpd)
GET /v1/videos/:id/manifest.m3u8
-> #EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=4000000,RESOLUTION=1920x1080
/videos/abc/1080p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2500000,RESOLUTION=1280x720
/videos/abc/720p.m3u8
...
# Player fetches a segment
GET /cdn/videos/abc/1080p/segment_0042.ts
-> binary segment bytes (10 seconds of video)
# View event (analytics)
POST /v1/videos/:id/events
{ event: "play" | "pause" | "seek" | "bitrate_change" | "buffering", ts }The manifest is small (~1KB) and lists every available bitrate + the segment playlist for each. The segments are served from the CDN — the manifest URL itself may be cacheable for hours.
Data model.
Videos metadata (sharded SQL, by video_id):
videos (
id PK, creator_id, title, description, status,
duration_sec, source_url, manifest_url,
created_at, published_at, view_count
INDEX (creator_id, created_at),
INDEX (status, published_at)
)Transcode jobs (job queue):
transcode_jobs (
id PK, video_id, source_url, status: "queued" | "running" | "done" | "failed",
presets_json, priority, created_at, started_at, completed_at
)Segment inventory (denormalized table for fast manifest generation):
segments (
video_id, bitrate_preset, segment_number, s3_url, duration_ms,
PRIMARY KEY (video_id, bitrate_preset, segment_number)
)Object storage layout (S3):
bucket: nocap-video-source
key: {video_id}/source.{ext}
bucket: nocap-video-segments
key: {video_id}/{preset}/segment_{N:06d}.ts
key: {video_id}/{preset}.m3u8 -- playlist per preset
key: {video_id}/master.m3u8 -- master manifestCDN edge cache (CloudFront / Cloudflare):
GET /videos/{video_id}/{preset}/segment_{N}.ts
-> cache key: video_id + preset + N
-> TTL: 1 year (segments are immutable once published)Critical invariant: segments are immutable. A given (video_id, preset, N) tuple always returns the same bytes. This is what enables aggressive CDN caching — no revalidation needed.
Deep dive: HLS, DASH, and adaptive bitrate.
HLS (HTTP Live Streaming). Apple's protocol. A master .m3u8 manifest lists variant streams (one per bitrate). Each variant has its own .m3u8 playlist listing segments (.ts files, ~10 seconds each). The player fetches the master, picks a variant based on bandwidth, fetches segments sequentially, and switches variants between segments.
DASH (Dynamic Adaptive Streaming over HTTP). MPEG standard, similar to HLS but uses .mpd (XML) manifests and .m4s segments. Used by YouTube, Netflix on some platforms. Functionally equivalent to HLS — both deliver ABR video over HTTP.
Why both? Safari only supports HLS natively; some smart TVs only support DASH. Most services transcode to both formats from the same source segments (costly) or use a single format with a polyfill player (e.g. hls.js for HLS in non-Safari browsers).
Why segment into 10-second chunks?
- Too short (1s): too many HTTP requests, per-request overhead dominates, manifest gets huge.
- Too long (60s): ABR switching is sluggish (bandwidth changes have to wait 60s to take effect); rebuffer after a network blip is costly (must re-fetch the 60s segment).
- 10s is the sweet spot: ABR reacts within 10s, manifests stay small, segment count is manageable (1-hour video = 360 segments per bitrate).
Why multiple bitrates? A viewer on a 50 Mbps fiber connection can stream 4K; a viewer on a 1 Mbps mobile connection can only stream 240p. Without multiple bitrates, either the 4K viewer gets a degraded 240p experience (wasted bandwidth), or the mobile viewer can't play 4K at all (rebuffer storm). Adaptive bitrate lets each viewer get the best quality their network supports.
The ABR algorithm. The player estimates bandwidth by measuring segment fetch time. The classic algorithm (used by Netflix's player early on): if measured_bw > next_bitrate_threshold for 3 consecutive segments, switch up; if measured_bw < current_bitrate_threshold for 1 segment, switch down. The asymmetry (3 to switch up, 1 to switch down) is intentional — switching up too eagerly causes rebuffers; switching down quickly prevents them. Modern players use buffer-based algorithms (BOLA) that consider both bandwidth and current buffer health.
Deep dive: the transcoding pipeline.
FFmpeg as the workhorse. Every video platform uses FFmpeg (or a wrapper around libavcodec). A typical command for one preset:
ffmpeg -i source.mp4 \
-c:v libx264 -preset fast -crf 23 \
-vf scale=1280:720 \
-b:v 2500k -maxrate 2680k -bufsize 3750k \
-c:a aac -b:a 128k \
-f hls -hls_time 10 \
-hls_playlist_type vod \
-hls_segment_filename 720p/seg_%06d.ts \
720p.m3u8Per-segment vs whole-file transcoding. FFmpeg can transcode the whole file in one process (simple, but a single failure restarts from scratch) OR split the source into ranges and transcode in parallel (faster, more complex). For a 2-hour 4K video, single-process can take 6+ hours; parallel-by-range with 16 workers can do it in ~30 min.
Hardware acceleration. Modern transcoding uses GPU (NVENC on NVIDIA) or dedicated ASICs (AWS MediaConvert, Intel QSV). 5-10x faster than CPU x264. Cost: GPU instances are 5-10x more expensive per hour, but the speedup more than compensates.
Ladder design. The 'ladder' = the set of bitrates offered. Naive: 240p/480p/720p/1080p at fixed bitrates. Modern (Netflix-perceptual): use VMAF (Video Multimethod Assessment Fusion) to tune bitrates per content — cartoons compress better than action movies, so the ladder adapts per video. This saves 20-50% on bandwidth.
The job queue. Transcoding is CPU/GPU intensive and bursty (creators upload at all hours). A priority queue: paid creators and viral-candidate videos first; low-traffic back-catalog last. Workers autoscale based on queue depth — depth > threshold triggers scale-up; idle workers scale-down after 10 min.
Failure handling. A transcode worker can crash mid-segment. Mitigation: per-segment idempotency (re-running FFmpeg on the same source range produces identical output); resume from the last completed segment; dead-letter jobs that fail >3 retries.
CDN delivery and bottlenecks.
CDN cache key. video_id + preset + segment_N. Segments are immutable, so TTL is effectively infinite (1 year). The CDN serves ~95% of requests; origin egress stays manageable.
Cache miss handling. On a miss, the CDN fetches from origin (S3), serves the user, and caches. First viewer of a new segment pays origin latency; subsequent viewers hit edge.
Thundering herd (cache stampede). A viral video drops; 10K viewers hit the same segment simultaneously. All miss, all fetch from origin, origin gets crushed. Mitigation: request coalescing at the CDN (one origin fetch, all waiters share the response); origin shield (a CDN tier that sits in front of origin and coalesces).
Pre-fetching. Player predicts which segments will be needed next and pre-fetches them. Reduces TTFF for seeks and smooths ABR transitions.
Failure modes.
- CDN PoP failure. A PoP goes down; viewers in that region fall back to a farther PoP (higher latency, but functional). Mitigation: anycast DNS reroutes to next-nearest PoP.
- Origin (S3) failure. Rare (S3 has 11 nines), but if it happens the CDN can serve cached content until S3 recovers. Mitigation: multi-region S3 with cross-region replication; CDN shield in front of origin.
- Transcode pipeline backlog. A viral video drops and creators upload 10x normal; queue depth explodes. Mitigation: autoscale workers on queue depth (5 min to spin up); priority queue for paid creators.
- Player bug causes ABR thrashing. Player switches bitrates every segment, causing quality flicker. Mitigation: hysteresis (3 segments to switch up, 1 to switch down); cap switch frequency.
- Hot segment cache eviction. CDN has finite cache; old segments evicted under pressure. Mitigation: pin hot catalog at edge; use cache-tier separation (SSD for hot, HDD for warm).
- Bufferbloat in mobile. Mobile latency is high; ABR estimates lag actual bandwidth. Mitigation: shorter segments on mobile or low-latency HLS (LL-HLS, 2-3s segments).
Scaling strategy and trade-offs.
CDN-first architecture. Origin (S3 + API) only handles manifest fetches (~5% of traffic) and cache misses. CDN absorbs 95%. Most products use CloudFront, Akamai, or Cloudflare; Netflix runs their own CDN (Open Connect) — only viable at Netflix scale.
Multi-CDN. For global reach, use multiple CDNs — pick the best per region per video (CDN routing based on performance telemetry). Costs more but improves availability and latency.
Transcoding autoscaling. Workers are stateless; autoscale on queue depth. Spot instances for non-priority backlog; on-demand for priority. Transcoding is the largest compute cost — saving 30% via better ladders = millions of dollars.
Multi-region origin. S3 buckets in 3+ regions with cross-region replication. CDN fetches from nearest region.
Edge compute for manifests. Manifest generation can be done at the edge (per-user personalized manifests for ad insertion). Push logic to the CDN edge function (Cloudflare Workers / Lambda@Edge).
Trade-offs made explicit.
- We chose HLS+DASH over a single protocol — gained device coverage, lost storage cost (transcode to both formats). Most services pick one and polyfill.
- We chose 10-second segments — gained balanced ABR reactivity vs request overhead, lost ultra-low latency (LL-HLS gets to 2-3s for live).
- We chose 5 bitrates (240p to 4K) — gained broad device/network coverage, lost transcode cost (5x source bytes). Could be 3 bitrates to save 40% storage at the cost of mobile UX.
- We chose pre-transcoded (VOD) over on-demand transcode — gained CDN-cacheable immutable segments, lost storage cost and upload-to-publish latency (must finish all bitrates before publish).
- We chose CDN over origin serving — gained 95% origin offload and edge latency, lost direct control of bytes (must respect CDN cache behavior).
- We chose hardware-accelerated transcoding (NVENC) — gained 5-10x throughput per instance, lost some quality (NVENC slightly worse than x264 slow preset at same bitrate) and per-instance cost.
Why are HLS / DASH videos split into ~10-second segments rather than streamed as one file?
Pick one answer.
A viral video drops and 10,000 viewers start watching the same segment within 1 second. What's the risk and the mitigation?
Pick one answer.
Engineering mental model
Mental model. Think of Design Video Streaming as a deliberate boundary in a system. The boundary exists because something becomes harder to manage when everything is done in one place: latency, scale, failure isolation, consistency, cost, or team ownership. The useful question is not “what does Design Video Streaming mean?” but “what pressure makes this boundary worth introducing, and what new failure mode does it create?”
Before choosing Design Video Streaming, name the workload, the critical user path, the dominant bottleneck, the failure you are trying to absorb, and the trade-off you are willing to accept. If you cannot name those five things, the design is probably premature.
// Pseudocode
request = receive()
result = design_video_streaming(request)
return result
// Production questions:
// 1. What happens on timeout?
// 2. Can this operation be retried safely?
// 3. What is the bottleneck?Back-of-the-envelope reasoning
Numerical lens: write down traffic, payload size, read/write ratio, peak multiplier and durability target before choosing a component. The numbers should justify the architecture.
Interactive thought experiment: Design Video Streaming
Change the variables below and predict what breaks first in Design Video Streaming. The production lab can later reuse these same inputs.
Change one variable at a time. Predict the failure mode first, then move the slider and see whether your mental model matches the simplified system response.
If you are stuck on Design Video Streaming, start by drawing the request path and marking every network hop, stateful component, queue, cache and failure boundary. Then estimate where the system will saturate.
You increase traffic by 10× in a system using Design Video Streaming. What should you inspect first?
Pick one answer.
Which statement is the safest engineering habit when using Design Video Streaming?
Pick one answer.
You have dashboards for traffic, latency, errors and saturation. You can change the architecture, but every change has operational cost.
Production scenario: your system uses Design Video Streaming, traffic suddenly spikes, and p99 latency doubles. What is your first move?
Interview drill
Answer this without notes: When would you choose Design Video Streaming, and when would you intentionally avoid it? Mention at least one bottleneck it addresses, one failure mode it introduces, and one alternative. Then quantify the workload you are designing for.
For Design Video Streaming, separate producer speed from consumer speed. The key design question is what happens when production temporarily exceeds processing capacity: queue it, shed it, slow producers down, or degrade the feature.
Numerical sanity check
A simple queue sanity check: if producers create 8,000 messages/s and consumers process 6,000 messages/s, backlog grows at roughly 2,000 messages/s until the imbalance is corrected.
Do not optimize for a memorized definition. Reason from the workload and failure mode.
A downstream service slows down while Design Video Streaming keeps accepting traffic. Would you rather apply backpressure, queue more work, shed load, or degrade? Explain the trade-off.
Pick one answer.
What you gain, what you pay
- +Adaptive bitrate gives every viewer the best quality their network supports.
- +CDN absorbs 95% of egress; origin stays small.
- +Immutable segments enable aggressive edge caching with no revalidation.
- +Hardware-accelerated transcoding (NVENC) cuts cost 5-10x vs CPU.
- −5-bitrate ladder means 5x source bytes in storage and transcode cost.
- −10-second segments add ~10s of latency vs real-time streaming (LL-HLS reduces to 2-3s).
- −Upload-to-publish latency requires all bitrates transcoded first (long-tail creators wait).
- −HLS+DASH dual format doubles storage cost — most pick one and polyfill.
How this breaks in production
- Thundering herd on viral video segment drops — needs request coalescing + origin shield.
- Transcode pipeline backlog from viral uploads — needs autoscaling on queue depth + priority queue.
- CDN PoP failure — needs anycast DNS rerouting to next-nearest PoP.
- ABR thrashing from per-segment bitrate switches — needs hysteresis (3 up, 1 down).
- Hot segment eviction from CDN cache pressure — needs hot-catalog pinning at edge.
- Mobile bufferbloat causes ABR bandwidth mis-estimation — needs shorter segments or LL-HLS on mobile.
- Transcode worker crash mid-segment — needs per-segment idempotency and resume from last completed segment.
Don't fall into these traps
- •Streaming the source file as one big file — no adaptive bitrate, no CDN caching efficiency.
- •1-second segments — per-request overhead dominates, manifests balloon.
- •60-second segments — ABR reacts slowly, rebuffer after blips is costly.
- •Only one bitrate — viewers on bad networks can't play, viewers on good networks get degraded quality.
- •Origin serving without CDN — origin gets crushed at 1M concurrent streams.
- •Sync transcoding (transcode-on-demand at viewer time) — origin latency kills TTFF.
- •Forgetting that segments must be immutable — mutable segments break CDN caching.
- •Using x264 software encode in production — 5-10x cost vs hardware NVENC.
Real systems using this
How real systems implement this
- Netflix — Open Connect (their own CDN with appliances in ISP data centers), HLS+DASH, per-title perceptual ladders using VMAF, hardware transcoding at scale. Documented in Netflix Tech Blog posts on Open Connect and per-title encoding.
- YouTube — DASH as the primary protocol (HTML5 player), VP9/AV1 codecs for newer content, multi-CDN delivery, transcoding pipeline with FFmpeg + custom optimizations.
- Twitch — Live variant: LL-HLS for sub-second latency, transcode pipeline runs in real-time (not pre-encoded), edge POPs at ISP peering points for low-latency ingest.
Practice saying it out loud
- Q1Design Netflix. How do you serve video to 1B viewers without crushing your origin?
- Q2Why does HLS split video into 10-second segments? What if you used 1-second or 60-second segments?
- Q3A viral video drops and 10K viewers hit the same segment simultaneously. What happens?
- Q4How does adaptive bitrate work? Walk me through a viewer whose bandwidth drops mid-video.
- Q5How would you reduce transcoding cost by 30% without harming viewer experience?
Further reading & references
Core explanations are original NO CAP material. External references are provided for deeper study and standards.
What next?
Mark as understood once the mental model clicks.
Next recommended
Design YouTube