Blueprint
Media & files intermediate

Design YouTube

One early decision — cut every video into segments — quietly enables everything else: parallel transcoding, adaptive playback, and a CDN bill you can actually control.

asked at Google · Netflix · Meta Frequently asked ~40 min walkthrough
01

Scope the requirements

Scope ruthlessly: this question is upload and playback of huge files. Search, recommendations and comments are separate systems — name them and cut them, or the transcoding pipeline you're actually being tested on gets ninety seconds of airtime.

Functional — what it must do

  • Upload videos — tens of GB — with title and metadata.
  • Stream videos with quality that adapts to the viewer's bandwidth.
  • Playback starts fast and stays smooth on poor connections.
  • Resumable uploads: a dropped connection at 90% must not restart the transfer.
  • Explicitly cut: search, recommendations, comments, monetisation, live streaming — each is its own interview.

Non-functional — the numbers that shape the design

  • Playback start under ~1–2 s; no rebuffering on a stable connection at any bandwidth (adaptive bitrate is a requirement, not a feature).
  • Availability over consistency — a video appearing a minute late is fine; playback failing is not.
  • Scale: ~1M uploads/day, 100M watches/day, global audience.
  • Originals stored durably forever — re-uploading is not an option.
  • Uploads of 10+ GB must survive flaky client connections.
Say this out loud

Watch-to-upload is 100 to 1, but the write path is where the engineering is: getting a 10 GB file in reliably and turning it into something a phone on 3G can play. I'll design the pipeline first, then make the read path a CDN problem.

02

Back-of-envelope estimates

The request rates are almost embarrassingly small — the numbers that matter are bytes: daily ingest, one upload's transfer time, and the CDN invoice. Those three drive every design decision here.

QuantityEstimateHow you got there
Uploads~12/s1M/day ÷ 86,400 s. Trivial as a request rate; enormous as bytes.
Watches~1,200/s100M/day; 100:1 watch:upload ratio. Metadata reads, then the CDN takes over.
New storage~150 TB/day~500k daily uploaders × ~300 MB average — call it 50 PB/year before transcoded renditions roughly double it.
One 10 GB upload~13 min at 100 Mbpsany real connection drops in that window — resumable chunked upload is mandatory, not nice-to-have.
CDN egress~$150k/dayorder-of-magnitude for ~5M daily watchers at CDN per-GB rates — the bill that makes long-tail-from-origin worth designing for.
Metadata~365M rows/yra rounding error next to the blobs; metadata scale is never the problem in this question.
Say this out loud

Twelve uploads a second but 150 terabytes a day — this system is about bytes, not requests. So the architecture keeps video bytes off my application servers entirely: clients talk to S3 and the CDN directly, and my services only ever move metadata.

03

Sketch the API

The API splits cleanly in two: a control plane my services own, and a data plane where the client talks straight to S3 and the CDN. The interesting decision is that playback never goes through my API at all.

POST/api/v1/videosbody: { title, size, mimeType } → { videoId, multipart presigned URLs }. Creates the metadata row in uploading state; bytes never touch this service.
PATCH/api/v1/videos/{id}/chunksbody: { chunkId, etag } — client reports upload progress; server verifies against S3 before trusting it.
GET/api/v1/videos/{id}→ metadata including the manifest URL. This is the only API call on the watch path.
GET{cdn}/videos/{id}/manifest.m3u8HLS manifest listing every rendition and its segment URLs. Served by the CDN, not by me.
GET{cdn}/videos/{id}/720p/segment_042.tsone ~4 s segment at one bitrate — the unit of everything: transfer, caching, quality switching.
  • The manifest is the API for playback. After one metadata call, the player deals exclusively with the CDN: fetch manifest, measure bandwidth, pick a rendition, fetch segments, re-decide every few seconds. My services could be down and playback of anything already published keeps working — worth saying out loud, it reframes the availability story.
  • Presigned multipart URLs mean upload auth is decided once by my control plane, then S3 enforces it per-part — my servers never proxy a byte in either direction.
04

Data model

Two tables and a bucket. The metadata row does double duty as the upload and processing state machine, which is what keeps resumability honest.

video_metadata
videoId PK · uploaderId · title · status (uploading/processing/ready) · chunks[{id, etag, state}] · manifestUrl · renditions[]
Partitioned by videoId. The chunk-state array is what a resuming client reads to know what to skip.
users
userId PK · handle · channel metadata
Boring on purpose — nothing here is on a hot path.
S3 buckets
originals/ · segments/{videoId}/{rendition}/ · manifests/
Originals kept forever (durably, cold-tiered); segments are what the CDN pulls.
The database call

Cassandra for video metadata: point lookups by videoId, no cross-entity transactions anywhere, and I get multi-region replication and linear write scale for free — availability over consistency is exactly Cassandra's contract. Honestly, at 365M rows a year Postgres would also cope for a long time; if the interviewer prefers it I'd take it and name the multi-region replication story as the thing I'd have to build myself. The blobs were never going in a database either way.

05

High-level design

Upload is a control-plane handshake followed by a client-to-S3 transfer; an event-driven DAG then fans one video out into dozens of segment-level transcode jobs; playback is a manifest and a stream of segments served almost entirely from CDN edges.

client edge service data pipeline Uploader Viewer HLS player API gateway CDN manifests + segments Video service stateless control plane Cassandra video metadata S3 originals + segments Orchestrator Temporal DAG Transcode workers one job per segment manifest, segs mark ready POST /videos renditions metadata segment jobs origin pull S3 event GET /videos multipart
notice video bytes never pass through the service lane — upload goes client→S3, playback goes CDN→client, and the whole dashed pipeline is asynchronous behind an S3 event.
  1. Upload: client POSTs metadata → video service writes a uploading row to Cassandra and returns presigned multipart URLs → client uploads 5–10 MB parts straight to S3, PATCHing progress as it goes.
  2. On completion, an S3 event kicks the orchestrator: split the original into segments, then fan out one transcode job per segment per rendition — H.264/H.265 × 360p–4K — all in parallel.
  3. Workers write finished segments back to S3, passing data between steps as S3 URLs; a final step generates the HLS/DASH manifests and flips the Cassandra row to ready.
  4. Watch: viewer fetches metadata (one API call) → pulls the manifest from the CDN → player measures bandwidth and requests segments at whatever bitrate the last few seconds justify.
  5. The CDN serves hot segments from edge; misses pull from S3 once and stay cached — segments are immutable, so TTLs are effectively infinite.
06

Deep dives — where the interview is won

Phase 01

Segment first — the one decision everything else hangs on

Cutting every video into ~4-second segments looks like an encoding detail, but it's the load-bearing decision of the whole design. Segments make transcoding parallel: a two-hour film is ~1,800 independent jobs per rendition, so wall-clock transcode time is minutes on a big worker pool instead of hours on one machine. Segments make playback adaptive: the client can change bitrate at every segment boundary because each segment is independently decodable. And segments make the CDN efficient: the cache unit is a few MB that many viewers share, not a multi-GB file.

The naive alternative — progressive download of one big MP4 — fails the stated requirements one by one: no mid-stream quality switching, so the low-bandwidth requirement dies; no parallel transcode, so publish latency dies; cache granularity of whole files, so the CDN hit rate dies. When an interviewer asks 'why not just serve the file?', this is the answer, requirement by requirement.

upload segments cdn Full video seg 1 seg 2 seg N CDN edge split cache
one upload becomes N segments. transcode, ABR, and CDN all key on segment as unit.
Trade-offSegmentation multiplies object count by four or five orders of magnitude and adds a manifest layer the client must understand — machinery you'd never accept for a system serving short clips to uniform-bandwidth clients.
Phase 02

The transcoding pipeline is a DAG with an orchestrator, not a script

Post-upload processing is a genuine dependency graph: split → per-segment transcodes (fully parallel, per codec and resolution) → audio and thumbnail branches alongside → manifest generation, which needs every segment done. At a million uploads a day some worker is always dying mid-job, so I run it on a workflow orchestrator — Temporal or Step Functions — which owns the graph state, retries individual failed segment jobs with backoff, and resumes a half-finished video without redoing completed work.

Two rules keep the pipeline sane. Workers exchange data as S3 URLs, never payloads — steps stay stateless and any retry can run on any machine. And every step is idempotent — writing segment 42 at 720p twice must be harmless, because with retries it will happen. Hand-rolling this coordination in application code is the classic way this question goes sideways in the last fifteen minutes.

orch stages Orchestrator Temporal Probe Split Transcode fanout Manifest
orchestrator drives a DAG. each node is idempotent; retries and fan-out are first-class.
Trade-offAn orchestrator adds an infrastructure dependency and per-step latency overhead versus a monolithic ffmpeg job — negligible against minutes of transcode, decisive when the alternative is re-transcoding a 4K film because one segment job hiccuped.
Phase 03

Resumable upload: multipart with server-verified ETags

Thirteen minutes is the happy-path transfer time for 10 GB, so the design assumption is that every large upload fails at least once. S3 multipart is the mechanism: the control plane initialises an upload and hands out presigned per-part URLs, the client pushes 5–10 MB parts — in parallel, which also fills fat pipes — and records each part's ETag via PATCH. On resume, the client reads the chunk-state array from metadata and skips what's done.

The subtle point that separates candidates: don't trust the client's claim. Before completing the multipart upload, the server verifies the reported parts against S3 (ListParts) — a buggy or malicious client claiming chunks it never sent would otherwise publish a corrupt video. The client's report is an optimisation for progress UX; S3 is the truth.

client svc s3 Uploader Upload API which chunks? S3 multipart list gaps PUT part + ETag
each chunk PUT gets an ETag; client resumes by asking which chunks are missing.
Trade-offChunk-state tracking adds a PATCH round-trip per part and a verification step at completion — versus single-PUT uploads where one dropped packet at 99% costs the user thirteen more minutes.
Phase 04

The CDN is the biggest line item — serve the long tail from origin

At roughly $150k/day of egress, the CDN is where this system's money goes, and view distribution is brutally skewed: a small percent of videos take nearly all traffic while the long tail gets single-digit views. Caching everything at edge pays premium rates to store segments nobody re-requests. The optimisation: route popular content through the CDN and let long-tail requests go to origin (S3 or cheap origin shields) directly — a popularity threshold on the URL the metadata service hands out is enough.

For genuinely hot content the same skew works in my favour: a new release's segments are requested thousands of times per edge node, so hit rates approach 100% and origin barely notices launch traffic. The remaining hot spot is metadata — a viral video's Cassandra partition — absorbed by replication plus a small LRU cache in front, and it's honest to note view-count-style features are exactly why that cache earns its keep.

viewer cdn origin Viewer Edge hit 99% Long-tail miss Origin S3 hit miss fetch
hot titles on CDN edge; unusual titles fetch from origin. 99% hit rate typical.
Trade-offLong-tail viewers get origin latency — maybe 500 ms extra on first segment — in exchange for cutting a majority-of-catalogue cache bill; the popularity threshold is a knob trading user experience directly against dollars.
07

Follow-ups you should expect

Would you pre-transcode every rendition for every video?
No — the long tail makes that wasteful: most videos never get watched at most bitrates. I'd always produce a small core set (say 360p and 720p) so playback works day one, and transcode the expensive renditions — 4K, extra codecs — lazily on first demand or when early view counts signal popularity. Storage and CPU saved on the tail pays for slightly worse first-viewer quality on unpopular videos.
How would you do view counts?
Not with an UPDATE per view — a viral video would serialise on one hot row. Playback events flow into Kafka, an aggregator batches counts and flushes every few seconds, and the displayed number is approximate-but-recent. Exactness matters only where money does — ad billing — and that's a separate reconciled pipeline over the same event stream.
How do you stop people ripping paid or private content?
Signed URLs on manifests and segments — short-lived tokens the CDN validates, so links can't be shared usefully. For real content protection that's not enough, since the client must decrypt to play: DRM (Widevine/FairPlay) encrypts segments with keys brokered by a licence server. I'd scope DRM out but name it — signed URLs stop hotlinking, DRM stops copying, and interviewers like hearing the distinction.
What changes for live streaming?
The DAG dies — there's no complete file to fan out; transcoding happens on a continuous stream with a seconds-long latency budget. Ingest switches to RTMP/WebRTC, segments shrink to 1–2 s, and manifests update continuously as new segments appear. Storage flips too: the CDN caches a sliding window rather than an immutable library. It shares components but it's genuinely a different system.
A transcode worker dies halfway through a 4K film — what happens?
Almost nothing — that's the point of segment-level jobs under an orchestrator. Temporal notices the timed-out segment job and reschedules it on another worker; every other segment's work is untouched because steps are idempotent and pass data via S3. The blast radius of a worker death is one segment, not one video.

Where candidates lose the room

  • Designing playback as 'download the file, then play' — progressive download fails the adaptive-bitrate requirement and usually signals the candidate hasn't met HLS/DASH.
  • Skipping segmentation, then having no answer for parallel transcode, mid-stream quality switching, or CDN cache granularity — three follow-ups lost to one omission.
  • Treating transcoding as a magic box ('a service transcodes it') with no DAG, no orchestration, no retry story — the pipeline is the technical core of this question.
  • Routing video bytes through application servers on upload or playback instead of presigned S3 and the CDN.
  • Claiming resumable upload without chunk-state tracking, or trusting client-reported chunks without verifying ETags against S3.
  • No manifest concept — without it there's no coherent story for how the player discovers renditions and switches between them.

Concepts this leans on

Found a bug or want more?

Tell me what's broken, missing, or wrong. Every message lands in my inbox.