# Design Youtube | System Design Question

"Design YouTube" sounds like it should be about the feed, the recommendations, the like button. It is not. Strip all of that away and what is left is the hardest part: a user hands you a 1 GB file, and a few minutes later millions of people on phones, laptops, and TVs, on fast and slow networks, all want to watch it without buffering. That single sentence is a storage problem, a compute problem, a bandwidth problem, and a cost problem stacked on top of each other. This post walks the whole thing the way a real 60-minute system design round goes: pin the scope with questions, turn the numbers into components, then go deep on the two things that actually make video hard - transcoding and streaming. The same design covers Netflix, Hulu, and any "upload a video, watch a video" product.

To put the scale in perspective: creators upload more than [500 hours of video to YouTube every minute](https://www.tubefilter.com/2019/05/07/number-hours-video-uploaded-to-youtube-per-minute), and the platform serves north of 2.5 billion monthly users. We are not going to design for that number on the whiteboard - no interviewer expects it - but every decision below is the decision that lets a system grow toward it instead of hitting a wall.

## Step 1: Ask questions before you draw anything

The prompt "design YouTube" is deliberately enormous. You can comment, share, like, subscribe, make playlists, go live, run a channel. Trying to build all of it is how you spend the whole hour drawing boxes and finish nothing. The first job is to cut the problem down to the part that is actually interesting, and you do that by asking. Here is a realistic exchange - clean answers, a punt, and a curveball all mixed in, because that is what you actually get.

> **You:** What are the two or three features that matter most here? I could spend the hour on the social graph or on the video pipeline, and those are very different systems.
>
> **Interviewer:** Focus on the core: a creator uploads a video, and a viewer watches it. Everything else is secondary.
>
> **You:** Good, that is the meaty part anyway. Which clients - web, mobile, TV?
>
> **Interviewer:** All three. Mobile apps, web browsers, and smart TVs.
>
> **You:** Roughly what scale? Daily active users, and how much do they watch?
>
> **Interviewer:** You tell me.

That "you tell me" is a punt, and it is where people freeze. Do not wait for a number - propose one, say it out loud, and write it down as an assumption. "I will assume 5 million daily active users, each watching about 5 videos a day, and I will design for that - stop me if you had a very different scale in mind." Now you own the scale instead of standing around waiting for it. Scale is the one input you cannot leave blank, because "5 GB a day" and "5 PB a day" are not the same system.

> **You:** Is there a cap on video size? That changes the storage and upload design a lot.
>
> **Interviewer:** Max 1 GB. Focus on small and medium videos.
>
> **You:** Do we need to support international viewers?
>
> **Interviewer:** Yes, most of them are international.
>
> **You:** Can I lean on cloud services - a managed blob store, a managed CDN - or am I building storage and a CDN from scratch?
>
> **Interviewer:** Lean on the cloud. Nobody builds their own blob storage in an interview.

That last answer matters more than it looks - I will come back to why "use the managed thing" is the correct engineering answer, not a shortcut. Now the curveball:

> **Interviewer:** Oh, and it should also do real-time live streaming of concerts.
>
> **You:** I can, but let me name the cost first: live streaming has a completely different latency budget - you cannot spend two minutes transcoding a frame someone is watching live - so it needs a different protocol and a different, lower-latency pipeline. If it is core, I will redesign around it; if it is a nice-to-have, I will design the on-demand system well and show you at the end exactly where live plugs in.
>
> **Interviewer:** Fair. Keep it on-demand, mention live at the end.

That is the whole move: use their answer when they give one, state an assumption when they punt, and when they bolt on scope, name its one-line cost and defer it instead of silently trying to do everything. If you want the general shape of this scoping step, I wrote it up separately in the [60-minute interview framework](/60-mins-system-design-interview-framework); here we apply it and move on.

### Requirements, written down

Say the scope back out loud so you and the interviewer are building the same thing. Keep it as two tight lists, one requirement per line - never a packed paragraph.

**Functional (what the system does):**

- A creator can upload a video file (up to 1 GB) from web, mobile, or TV.
- The system processes that video so it plays on any device and any network speed.
- A viewer can watch any uploaded video, starting almost instantly, without downloading the whole file first.
- Playback quality adapts to the viewer's network - sharp on fast Wi-Fi, lower resolution but uninterrupted on a weak mobile signal.
- Videos are served fast to viewers anywhere in the world.

**Non-functional (how well it does it):**

- **High availability** - the system stays up even when individual servers die.
- **Scalability** - it absorbs upload and view spikes without falling over.
- **Reliability** - an uploaded video is never silently lost or corrupted.
- **Low latency streaming** - playback starts in a second or two, wherever the viewer is.
- **Low cost** - serving video is bandwidth-heavy and bandwidth is expensive, so cost is a first-class design constraint, not an afterthought.

Notice cost is on the non-functional list. For most systems you would not bother. For video you must, and the estimate is about to show you why.

### Back-of-the-envelope: two numbers that shape everything

You do not need precise math here - you need the order of magnitude, because that is what picks your components. (If the arithmetic itself trips you up, I broke down the technique in [easier back-of-the-envelope estimation](/easier-back-of-the-envelope-estimation).) Two numbers matter: how much we store per day, and how much it costs to serve.

**Storage per day.** Assume 5 million daily active users, 10% of them upload one video a day, and the average video is 300 MB:

```
5,000,000 users x 10% uploading x 300 MB
= 500,000 videos x 300 MB
= 150,000,000 MB
= ~150 TB of new video every single day
```

That is *before* transcoding - and transcoding, as you will see, multiplies it, because we store several encoded copies of each video, not one. Call it a few hundred TB per day of durable storage growth. No single database holds that. This number is why the answer to "where do the videos live" is blob storage, not a table.

**Streaming cost.** Now the number that makes video its own beast. Assume each of our 5 million users watches 5 videos a day, each about 0.3 GB of data transferred, and we serve it from a CDN. Using a public rate like [Amazon CloudFront's](https://aws.amazon.com/cloudfront/pricing/) rough $0.02 per GB:

```
5,000,000 users x 5 videos x 0.3 GB x $0.02/GB
= $150,000 per day
= ~$55 million per year, just in bandwidth
```

That is the single most important number in the whole design. It tells you that **the CDN bill, not the servers, is the thing that will bankrupt this system** - so an entire section of the deep dive is going to be about spending less on it. Say that out loud in the interview; it shows you know where the real pain is.

> Quick vocabulary so nothing below stops you cold. **Blob storage** ("Binary Large Object") is a service built to hold huge unstructured files - videos, images - cheaply and durably; think Amazon S3 or Google Cloud Storage, not a row in a table. A **CDN** (Content Delivery Network) is a fleet of servers spread across the world that cache copies of your content near users, so a viewer in Mumbai is served from a machine in Mumbai, not from Virginia. **Transcoding** is converting a video into other formats and quality levels. We will define each of these properly as we hit it.

## Step 2: Build the high-level design from the numbers

Now build the system, one component at a time, each one justified by a number from above. Do not draw a finished diagram and narrate it backwards - reason each box into existence. There is a natural split the interviewer already hinted at, so we build two flows: the **upload path** (creator to stored, playable video) and the **stream path** (viewer to pixels on screen).

Start with the three coarse pieces every version of this design has, then refine:

- **Clients** - the web, mobile, and TV apps.
- **API servers** - handle everything that is *not* the raw video bytes: sign-up, fetching an upload URL, reading and writing video metadata, serving the watch page.
- **CDN** - handles the one thing that is huge: the video bytes themselves. When you press play, the video streams from the CDN, never from our API servers.

That last split is the key architectural decision, and it comes straight from the cost number. Video bytes are enormous and latency-sensitive; routing them through our own servers would need a colossal, globally distributed fleet - which is exactly what a CDN already is. So metadata goes through our servers, video goes through the CDN. Hold that thought and let us build the upload path, because that is where the real machinery lives.

### The upload path: reasoning each component into existence

**The video file itself needs somewhere to land.** We just computed ~150 TB a day of raw video. Candidates: a relational database (no - it is built for structured rows, not gigabyte files), a distributed file system we run ourselves (possible, but operationally brutal), or managed blob storage (S3, GCS). Pick **blob storage**. It is purpose-built for exactly this - cheap per-GB, effectively infinite, durable by default - and the interviewer already blessed leaning on the cloud. Call this bucket **original storage**; it holds the untouched file the creator uploaded.

**The video's information needs a different home.** Title, description, uploader, duration, resolution, the URLs of the processed files, view count - this is small, structured, and queried constantly ("show me this video's details"). That is a database, not a blob store. Because we will have billions of these rows and heavy read traffic, this **metadata DB** has to be sharded (split across many machines) and replicated (copied for safety and read scaling). I will not re-derive how here - that mechanism is its own topic, covered in [how to scale databases](/how-to-scale-databases). For the interview, "sharded and replicated SQL or a wide-column store like Cassandra" is the right depth.

**Metadata reads will dwarf writes.** Every watch page load reads a video's metadata; a video is written once and read millions of times. That read/write skew is the textbook signal for a cache. Put a **metadata cache** (Redis or Memcached) in front of the DB so the hot videos' metadata is served from memory. (Which cache strategy you pick - read-through, write-through - is a real decision I will not re-explain here; see [caching strategies](/caching-strategies-you-should-know).)

**Many clients upload at once, and API servers die.** We need more than one API server, and we need traffic spread across them - so put a **load balancer** in front, and make the API servers **stateless** (they hold no session data locally; any server can handle any request). Stateless plus a load balancer is what lets us add servers during a spike and lose one without anybody noticing.

**A raw uploaded file cannot be watched as-is.** A 1 GB `.mov` off someone's camera will not play smoothly on a cheap Android phone on a 3G connection. It has to be converted - **transcoded** - into multiple formats and quality levels. That is heavy, slow, CPU-bound work, so it gets its own fleet: **transcoding servers**. Their output - the playable, multi-quality versions - goes into a second blob store, **transcoded storage**, kept separate from the pristine originals.

**Transcoding takes minutes, and we must not make the uploader wait synchronously for it.** When a transcode finishes, several things need to happen (copy to the CDN, flip the video's status to "ready", update the cache), and we do not want the transcoding server blocking on all of that. So it drops a message onto a **completion queue** - a message queue is just a durable buffer that holds "this job is done" events until a worker is free to handle them. A pool of **completion handler** workers pulls those events and updates the metadata DB and cache. The queue is what decouples the slow producer from the fast consumers.

That is the whole upload path, every box earned by a number or a constraint:

![High-level architecture of the YouTube video upload path: the creator uploads the raw file to original blob storage while metadata goes through a load balancer to stateless API servers, a metadata cache, and a sharded metadata database; transcoding servers convert the video, write outputs to transcoded storage, push them to the CDN, and emit a completion event to a queue that completion-handler workers drain to mark the video ready.](https://mermaid.ink/img/Zmxvd2NoYXJ0IFRECiAgICBVc2VyKFtDcmVhdG9yIGRldmljZV0pCiAgICBMQltMb2FkIEJhbGFuY2VyXQogICAgQVBJW0FQSSBTZXJ2ZXJzIC0gc3RhdGVsZXNzXQogICAgTUNbKE1ldGFkYXRhIENhY2hlKV0KICAgIE1EQlsoTWV0YWRhdGEgREI8YnIvPnNoYXJkZWQgYW5kIHJlcGxpY2F0ZWQpXQogICAgT1NbKE9yaWdpbmFsIGJsb2Igc3RvcmFnZSldCiAgICBUU1tUcmFuc2NvZGluZyBzZXJ2ZXJzXQogICAgVERTWyhUcmFuc2NvZGVkIGJsb2Igc3RvcmFnZSldCiAgICBDRE5bQ0ROIGVkZ2UgbmV0d29ya10KICAgIENRW1tDb21wbGV0aW9uIHF1ZXVlXV0KICAgIENIW0NvbXBsZXRpb24gaGFuZGxlciB3b3JrZXJzXQoKICAgIFVzZXIgLS0+fDEgdXBsb2FkIHJhdyB2aWRlb3wgT1MKICAgIFVzZXIgLS0+fDFiIHNlbmQgbWV0YWRhdGF8IExCCiAgICBMQiAtLT4gQVBJCiAgICBBUEkgLS0+IE1DCiAgICBBUEkgLS0+IE1EQgogICAgT1MgLS0+fDIgZmV0Y2ggcmF3fCBUUwogICAgVFMgLS0+fDNhIHN0b3JlIG91dHB1dHN8IFREUwogICAgVFMgLS0+fDNiIGVtaXQgZXZlbnR8IENRCiAgICBURFMgLS0+fDNhLjEgcHVzaCB0byBlZGdlfCBDRE4KICAgIENRIC0tPiBDSAogICAgQ0ggLS0+fG1hcmsgcmVhZHl8IE1EQgogICAgQ0ggLS0+IE1DCg==?type=png&bgColor=FFFFFF)

The flow, in order: (1) the client uploads the raw file straight to original storage while, in parallel, (1b) it sends the metadata through the load balancer to the API servers, which write it to the DB and cache. (2) Transcoding servers fetch the raw file. (3) When done, two things happen at once - (3a) outputs are written to transcoded storage and pushed to the CDN, and (3b) a completion event is queued, which the completion handler drains to (4) mark the video ready so the creator sees "your video is live."

### The stream path: why streaming is not downloading

Now the viewer. When you tap a YouTube video it starts almost immediately - you are not waiting for a 300 MB file to finish downloading. That is the difference between **downloading** (copy the entire file to the device, then play) and **streaming** (the device continuously pulls small pieces of the video and plays them as they arrive, discarding them after). Streaming is why playback starts in a second and why scrubbing to the middle of a two-hour video does not download the first hour.

The mechanism that makes this work over the plain web is a **streaming protocol** - a standardized way to chop a video into small chunks and describe them so a player can fetch them over ordinary HTTP. The two you should name are **Apple HLS** (HTTP Live Streaming) and **MPEG-DASH** (Dynamic Adaptive Streaming over HTTP). You do not need to memorize the wire details - they are low-level and covered well [in this streaming-protocols primer](https://www.dacast.com/blog/streaming-protocols/) - but you do need the one idea they share, because it powers the "quality adapts to your network" requirement. We will unpack that in the deep dive.

The stream path itself is refreshingly simple, and simple on purpose: **videos are served directly from the CDN.** The edge server closest to the viewer delivers the chunks, so latency is tiny and - critically - none of that bandwidth touches our own servers. The player asks our API for the watch-page metadata (one small request), then talks to the CDN for the actual video bytes. That clean split is exactly why we drew the API-vs-CDN line back at the start. If you want the full story of how a CDN places and serves that content near users, it is its own post: [how a CDN scales delivery](/content-delivery-network-how-to-scale-frontends).

At this point you have a working high-level design and you have spent maybe 25 minutes. Good. Now earn the offer in the deep dive.

## Step 3: Deep dive

Three things separate a real answer from a hand-wave: how transcoding actually works (and how you make it fast and fault-tolerant), how a video adapts its quality to the network, and how you avoid the $55M/year CDN bill eating the company. Reason past the obvious on each.

### Transcoding: from one raw file to a wall of playable versions

First, define it properly, because everything hangs on it. **Transcoding** (also called encoding) is converting a video from the format your camera produced into other formats and quality levels. Two terms live inside it:

- **Container** - the outer wrapper that holds the video track, audio track, and metadata together. You know it by the file extension: `.mp4`, `.mov`, `.avi`. Think of it as a box.
- **Codec** - the compression algorithm *inside* the box that shrinks the video while preserving quality. Common ones are H.264, VP9, and HEVC. A codec is why a two-hour movie fits in a few GB instead of a few hundred.

Why bother transcoding at all? Four reasons, each mapping to a requirement:

1. **Raw video is gigantic.** An hour of high-definition footage at 60 frames per second can be hundreds of GB. Storing and shipping that as-is is impossible; codecs compress it.
2. **Devices are picky.** A given phone, browser, or TV only supports certain containers and codecs. To play everywhere, you must produce several formats.
3. **Networks differ.** A viewer on fibre should get 1080p; a viewer on a crawling mobile connection should get 240p so it still plays. So you encode the *same* video at multiple resolutions and bitrates.
4. **Networks change mid-watch.** Someone walks from Wi-Fi into an elevator. The video must drop to a lower quality on the fly instead of freezing.

> **Bitrate** is how many bits per second the video uses - higher bitrate means more detail and a bigger file. A **quality ladder** is the set of versions you produce: say 240p at 0.7 Mbps, 480p at 2.5 Mbps, 720p at 5 Mbps, 1080p at 8 Mbps. One upload becomes four (or more) encoded outputs. That is the storage multiplier I warned about in the estimate.

**Now reason past the obvious.** The naive design is "one transcoding server takes the file and runs FFmpeg on it." That fails for two reasons: it is painfully slow for a 1 GB file, and different creators want different processing - one wants a watermark, one supplies their own thumbnail, one needs an extra codec. Hard-coding a fixed pipeline cannot express that. So you need a design that is both *parallel* and *configurable*.

The standard answer, borrowed from [Facebook's video processing engine](https://www.cs.princeton.edu/~wlloyd/papers/sve-sosp17.pdf), is to model transcoding as a **DAG** - a Directed Acyclic Graph. Plain words: a DAG is a set of tasks connected by "this must finish before that starts" arrows, with no cycles (nothing loops back on itself). It lets you say "split the file, then encode video and audio *in parallel*, then package the results," and independent branches run at the same time. The original file is split into video, audio, and metadata, and each flows through its own tasks:

![Transcoding modeled as a directed acyclic graph: the original video is split by a preprocessor into GOP chunks, then into video, audio, and metadata tracks; the video track fans out to inspection then multi-resolution encoding, plus thumbnail and watermark tasks, the audio track goes through audio encoding, and every branch converges on an assemble-and-package step that writes HLS and DASH segments to transcoded storage.](https://mermaid.ink/img/Zmxvd2NoYXJ0IExSCiAgICBSQVdbT3JpZ2luYWwgdmlkZW9dIC0tPiBQUkVbUHJlcHJvY2Vzc29yPGJyLz5zcGxpdCBpbnRvIEdPUCBjaHVua3NdCiAgICBQUkUgLS0+IFZbVmlkZW8gdHJhY2tdCiAgICBQUkUgLS0+IEFbQXVkaW8gdHJhY2tdCiAgICBQUkUgLS0+IE1bTWV0YWRhdGFdCiAgICBWIC0tPiBJTlNQW0luc3BlY3Rpb25dCiAgICBJTlNQIC0tPiBFTkNbVmlkZW8gZW5jb2Rpbmc8YnIvPjI0MHAgNDgwcCA3MjBwIDEwODBwXQogICAgViAtLT4gVEhVTUJbVGh1bWJuYWlsXQogICAgViAtLT4gV01bV2F0ZXJtYXJrXQogICAgQSAtLT4gQUVOQ1tBdWRpbyBlbmNvZGluZ10KICAgIEVOQyAtLT4gUEtHW0Fzc2VtYmxlIGFuZCBwYWNrYWdlPGJyLz5ITFMgYW5kIERBU0ggc2VnbWVudHNdCiAgICBBRU5DIC0tPiBQS0cKICAgIFRIVU1CIC0tPiBQS0cKICAgIFdNIC0tPiBQS0cKICAgIE0gLS0+IFBLRwogICAgUEtHIC0tPiBPVVRbKFRyYW5zY29kZWQgc3RvcmFnZSldCg==?type=png&bgColor=FFFFFF)

The tasks worth naming: **inspection** (is the file valid and not malicious?), **video encoding** (produce each rung of the quality ladder), **thumbnail** (generate or accept one), and **watermark** (overlay a logo if the creator wants). Behind this DAG sits a small scheduling system - a **preprocessor** that splits the file and builds the DAG, a **DAG scheduler** that breaks it into stages, and a **resource manager** that hands each task to a free worker from a pool. You do not need to draw all of that unless asked; knowing it exists and can say "a scheduler assigns DAG tasks to a worker pool" is interview-deep.

### Splitting the file: GOP chunks and why parallelism needs them

Here is the trick that makes both fast uploads and parallel transcoding possible: **you do not process the video as one blob, you chop it into small independent segments first.** Video is a sequence of frames, and frames are grouped into a **GOP** (Group of Pictures) - a short run of frames, usually a few seconds, that can be decoded on its own without needing the rest of the video. Because each GOP chunk is self-contained, you can:

- **Upload in parallel and resume on failure.** Split the file into chunks on the client, upload them independently, and if chunk 47 fails on a flaky connection you re-send only chunk 47, not the whole gigabyte.
- **Transcode in parallel.** Ship different chunks to different workers and encode them simultaneously, then stitch the outputs back together. A file that would take 20 minutes on one machine takes 2 on ten.

This chunking is also exactly what the streaming protocols want on the other end - HLS and DASH deliver video as a sequence of small segments - so the same segmentation serves both the write path and the read path. One idea, two payoffs.

### Adaptive bitrate: how the video picks its own quality

This is the requirement that reads like magic - the video gets sharper and blurrier on its own as your network changes - and the mechanism is simple once you have the pieces. When we transcoded, we produced the whole quality ladder as parallel sets of segments. Alongside them we produce a **manifest** - a small text file that lists every available quality and where each of its segments lives. In HLS this manifest is an `.m3u8` playlist; in DASH it is an `.mpd` file. It looks roughly like this:

```m3u8
#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=700000,RESOLUTION=426x240
240p/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2500000,RESOLUTION=854x480
480p/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=1280x720
720p/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=8000000,RESOLUTION=1920x1080
1080p/index.m3u8
```

The **intelligence lives in the player, not the server**, and that is the elegant part. The server just hosts a pile of segments and a manifest. The player fetches the manifest, then for each upcoming segment it measures how fast the last few downloads went and picks the highest-quality rung it can pull without running the buffer dry:

![Adaptive bitrate streaming: the video player requests the manifest, which lists every quality rung stored on the CDN edge (240p through 1080p); on a strong network the player pulls higher-bitrate 720p chunks, and when the network drops it pulls lower-bitrate 240p chunks instead, all from the same CDN.](https://mermaid.ink/img/Zmxvd2NoYXJ0IExSCiAgICBQW1ZpZGVvIHBsYXllcl0KICAgIE1BTltNYW5pZmVzdDxici8+bGlzdHMgZXZlcnkgcXVhbGl0eV0KICAgIHN1YmdyYXBoIExBRERFUltDRE4gZWRnZTogYWRhcHRpdmUgYml0cmF0ZSBsYWRkZXJdCiAgICAgIGRpcmVjdGlvbiBUQgogICAgICBTMTA4MFsxMDgwcCAtIDggTWJwcyBjaHVua3NdCiAgICAgIFM3MjBbNzIwcCAtIDUgTWJwcyBjaHVua3NdCiAgICAgIFM0ODBbNDgwcCAtIDIuNSBNYnBzIGNodW5rc10KICAgICAgUzI0MFsyNDBwIC0gMC43IE1icHMgY2h1bmtzXQogICAgZW5kCiAgICBQIC0tPnwxIHJlcXVlc3QgbWFuaWZlc3R8IE1BTgogICAgTUFOIC0tPiBMQURERVIKICAgIExBRERFUiAtLT58MiBzdHJvbmcgbmV0d29yazogcHVsbCA3MjBwIGNodW5rfCBQCiAgICBMQURERVIgLS0+fDMgbmV0d29yayBkcm9wczogcHVsbCAyNDBwIGNodW5rfCBQCg==?type=png&bgColor=FFFFFF)

The selection logic on the client is genuinely this straightforward - here it is in TypeScript, picking the best rung that fits the measured throughput with a safety margin:

```typescript
interface Rung {
  bitrate: number;     // bits per second this rendition needs
  resolution: string;  // e.g. "1280x720"
  playlistUrl: string; // segment playlist for this quality
}

// The ladder comes from the manifest, sorted highest bitrate first.
function selectRung(ladder: Rung[], measuredBps: number): Rung {
  // Only trust ~80% of the measured bandwidth as a buffer against jitter.
  const safeBps = measuredBps * 0.8;

  // Highest rung we can sustain, else the lowest so playback never stops.
  const affordable = ladder.find((rung) => rung.bitrate <= safeBps);
  return affordable ?? ladder[ladder.length - 1];
}

// After each segment downloads, re-measure and re-decide for the next one.
function onSegmentDownloaded(bytes: number, seconds: number, ladder: Rung[]): Rung {
  const measuredBps = (bytes * 8) / seconds;
  return selectRung(ladder, measuredBps);
}
```

The 0.8 safety margin is the whole art of it - be too greedy and the buffer empties and the video stalls, which viewers hate far more than a slightly softer picture. That is why players err toward the rung below what the network could technically handle.

### Decoupling with message queues: the parallelism that makes it not-slow

Go back to the upload path and look closely at the transcoding stages: download the chunk, then encode it, then package it. Written naively, each step waits for the one before - the encoder sits idle until the downloader hands it a file. Chain enough of those and latency piles up, and worse, a slow or failed stage stalls everything behind it.

The fix is to put a **message queue between each stage** instead of a direct call. A message queue (Kafka, RabbitMQ, AWS SQS) is a durable buffer: a producer drops a message in and moves on; a consumer picks it up whenever it is free. With queues between stages, the encoder no longer waits on the downloader - it pulls the next ready chunk from its inbound queue the instant it finishes the current one, and every worker stays busy. This is the "completion queue" from the high-level design generalized across the whole pipeline. It buys three things at once: **parallelism** (stages run independently), **resilience** (if the encoder pool is down, messages wait in the queue instead of being lost), and **elastic scaling** (a backlog in a queue is the exact signal to add more workers to that stage).

### Fast, safe uploads: presigned URLs and resumable chunks

Two upload refinements an interviewer likes to hear.

**Presigned URLs.** Notice in the diagram that the creator uploads the raw file *straight to blob storage*, not through our API servers. That is deliberate - routing 1 GB files through our servers would waste enormous bandwidth and CPU on pure proxying. But you cannot let anyone write anywhere in your bucket. A **presigned URL** solves this: the client asks our API for permission, the API generates a temporary, single-purpose URL that grants "you may upload one file, to exactly this location, for the next N minutes," and the client uploads directly to storage with it. The bytes never touch our servers; the permission is scoped and expires. I will not re-explain the mechanism from scratch - I did that in [what are presigned URLs](/what-are-presigned-urls) - but generating one is only a few lines:

```typescript
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

const s3 = new S3Client({ region: "us-east-1" });

// Called by an authenticated API endpoint after checking the user may upload.
async function createUploadUrl(userId: string, videoId: string): Promise<string> {
  const command = new PutObjectCommand({
    Bucket: "youtube-original-storage",
    Key: `raw/${userId}/${videoId}.mp4`, // exact location the client may write
    ContentType: "video/mp4",
  });

  // URL is valid for 15 minutes, then it is useless.
  return getSignedUrl(s3, command, { expiresIn: 900 });
}
```

**Resumable chunked upload.** Because the client already splits the file into GOP-aligned chunks (see above), a dropped connection costs you one chunk, not the whole upload. And to shave latency further, you place **upload endpoints near users** - a creator in India uploads to an Asian endpoint, not across the Pacific - which is just the CDN idea applied to the write path.

### Cost optimization: how not to pay $55 million a year

Remember the number: serving video from the CDN could cost tens of millions a year, and it dominates everything else. The single most important optimization insight is about *access patterns*. YouTube viewing follows a **long-tail distribution**: a small number of videos are watched enormously, and a vast number are watched rarely or never. You do not need to treat all videos the same, and paying premium CDN rates to keep a video nobody watches warm at every edge is pure waste. Four moves follow directly:

1. **Only put popular videos on the CDN.** Serve the hot minority from the expensive, fast CDN, and serve the cold long tail from cheaper high-capacity storage servers of our own. Most requests hit the small hot set, so most traffic still gets the CDN's speed while the bill drops sharply.
2. **Encode the long tail on demand.** For rarely watched videos, do not pre-generate and store every rung of the quality ladder. Keep fewer versions and transcode extra qualities only if someone actually requests them.
3. **Serve regionally.** A video popular only in Brazil does not need to be replicated to edges in Japan. Distribute by where the demand actually is.
4. **At true scale, build your own CDN.** This is what Netflix did with [Open Connect](https://openconnect.netflix.com/) - placing their own caching boxes inside Internet Service Providers, right next to viewers. It is a giant project that only makes sense past a certain size, but it is the endgame for bandwidth cost.

Here is the whole system with the cost split baked in - the culmination of everything above:

![Combined YouTube architecture: creator devices request a presigned URL through the load balancer and stateless API servers backed by a metadata cache and sharded database, then upload directly to original storage; a transcoding pipeline of task queue, preprocessor and DAG, resource manager, and task workers writes results to transcoded storage and a completion queue drained by a completion handler; transcoded storage feeds both a CDN for hot videos and cheaper storage video servers for the cold long tail, and viewer devices are routed to the CDN for popular videos and to the storage servers for long-tail videos.](https://mermaid.ink/img/Zmxvd2NoYXJ0IFRCCiAgICBzdWJncmFwaCBDbGllbnRzCiAgICAgIFUxKFtDcmVhdG9yIGRldmljZV0pCiAgICAgIFUyKFtWaWV3ZXIgZGV2aWNlXSkKICAgIGVuZAogICAgTEJbTG9hZCBCYWxhbmNlcl0KICAgIEFQSVtBUEkgU2VydmVycyAtIHN0YXRlbGVzc10KICAgIE1DWyhNZXRhZGF0YSBDYWNoZSldCiAgICBNREJbKE1ldGFkYXRhIERCPGJyLz5zaGFyZGVkIGFuZCByZXBsaWNhdGVkKV0KICAgIE9TWyhPcmlnaW5hbCBzdG9yYWdlKV0KICAgIHN1YmdyYXBoIFRyYW5zY29kZVtUcmFuc2NvZGluZyBwaXBlbGluZV0KICAgICAgZGlyZWN0aW9uIExSCiAgICAgIFExW1tUYXNrIHF1ZXVlXV0KICAgICAgUFJFW1ByZXByb2Nlc3NvciBhbmQgREFHXQogICAgICBSTVtSZXNvdXJjZSBtYW5hZ2VyXQogICAgICBUV1tUYXNrIHdvcmtlcnNdCiAgICAgIFExIC0tPiBQUkUgLS0+IFJNIC0tPiBUVwogICAgZW5kCiAgICBURFNbKFRyYW5zY29kZWQgc3RvcmFnZSldCiAgICBDUVtbQ29tcGxldGlvbiBxdWV1ZV1dCiAgICBDSFtDb21wbGV0aW9uIGhhbmRsZXJdCiAgICBDRE5bQ0ROIC0gaG90IHZpZGVvc10KICAgIFZTWyhTdG9yYWdlIHZpZGVvIHNlcnZlcnM8YnIvPmNvbGQgbG9uZy10YWlsKV0KCiAgICBVMSAtLT58YSBhc2sgZm9yIHByZXNpZ25lZCBVUkx8IExCCiAgICBMQiAtLT4gQVBJCiAgICBBUEkgLS0+IE1DCiAgICBNQyAtLT4gTURCCiAgICBVMSAtLT58YiB1cGxvYWQgdmlhIHByZXNpZ25lZCBVUkx8IE9TCiAgICBPUyAtLT4gUTEKICAgIFRXIC0tPiBURFMKICAgIFRXIC0tPiBDUQogICAgVERTIC0tPiBDRE4KICAgIFREUyAtLT4gVlMKICAgIENRIC0tPiBDSCAtLT4gTURCCiAgICBVMiAtLT58d2F0Y2ggcG9wdWxhcnwgQ0ROCiAgICBVMiAtLT58d2F0Y2ggbG9uZy10YWlsfCBWUwo=?type=png&bgColor=FFFFFF)

### Error handling: assume everything fails

At this scale, failure is constant, so name how the system recovers rather than pretending it will not break. Split errors into two kinds:

- **Recoverable** - a single segment fails to transcode, a chunk upload times out. Retry a few times; if it keeps failing, return a clear error code.
- **Non-recoverable** - the file is a malformed or corrupt video. Stop, do not retry forever, and tell the client the file is bad.

A retry helper for the recoverable case, with a cap so it does not loop forever on a truly broken job:

```typescript
async function withRetry<T>(
  task: () => Promise<T>,
  maxAttempts = 3,
): Promise<T> {
  let lastError: unknown;
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await task();
    } catch (err) {
      lastError = err;
      // Back off a bit longer each time before trying again.
      await new Promise((resolve) => setTimeout(resolve, attempt * 1000));
    }
  }
  throw lastError; // give up; caller marks the job failed, not "stuck"
}
```

The component-level playbook is mostly "because we made things stateless and replicated, recovery is boring": an API server dies and the load balancer routes around it; a transcoding worker dies and its task is retried on another; a cache node dies and reads fall back to a replica; the metadata DB primary dies and a replica is promoted. Boring recovery is the goal - it means no single failure takes the system down.

## Follow-up questions to expect

The core design is done; now the interviewer probes. These are the ones that come up most, with the crisp answer each wants.

**"A video goes viral - one video, 50 million views in an hour. What breaks?"** Not much, by design, because that video is exactly what the CDN is for - it gets cached at every edge and served locally, so origin load stays flat no matter how viral it goes. The hot spot to watch is that video's *metadata* row and cache key; protect it by caching aggressively and, if needed, replicating that key across cache nodes so a single Redis shard is not hammered.

**"The metadata DB is a single point of failure."** It is not, if we did step 2 right: it is replicated, so a replica is promoted when the primary dies, and sharded, so no one node holds everything. Reads scale across replicas; writes go to the primary of the relevant shard.

**"How do you scale to 10x the users?"** The API tier is stateless, so scaling it is just adding instances behind the load balancer. The transcoding tier scales by adding workers - the queues already tell you which stage is backed up. The database scales by adding shards. The CDN scales by definition. The design has no component that requires a rewrite to 10x, which is the point of building it this way.

**"How do you stop abuse - someone uploading 10,000 videos a minute, or hammering the upload-URL endpoint?"** Rate-limit at the API tier, per user and per IP, before any expensive work starts. That is its own well-defined component; I broke down the algorithms (token bucket, sliding window) in [design a rate limiter](/design-a-rate-limiter-system-design-question).

**"How do you protect copyrighted videos from being stolen?"** Three layers, strongest first: **DRM** (Digital Rights Management systems like Google Widevine, Apple FairPlay, Microsoft PlayReady) that encrypt the stream and control playback; **AES encryption** with an authorization check so only entitled users can decrypt; and **visual watermarking** as a deterrent and traceability measure.

**"What about live streaming?"** It shares the DNA - upload, encode, stream - but the latency budget is brutal: you cannot spend minutes transcoding a frame someone is watching in real time. So it uses a lower-latency protocol, processes tiny chunks continuously instead of batching, and needs error handling that fails fast rather than retrying slowly. Same skeleton, tighter timing. This is exactly the curveball I deferred in step 1, now answered.

## What you just built

Step back and see the shape of the answer, because the shape is the lesson. Video is not one hard problem, it is four - storage, compute, bandwidth, and cost - and the design solves each with a boring, proven piece: blob storage for the gigabytes, a parallel DAG pipeline for the transcoding, a CDN for global delivery, and a long-tail-aware split for the bill. The two ideas that carry the whole thing are **chunking** (GOP segments make uploads resumable, transcoding parallel, and streaming adaptive - all from one move) and **decoupling** (queues between stages so nothing waits on anything slow).

If you remember one thing from this walkthrough, remember where the pain is: for most systems the bottleneck is the database, but for video it is the **bandwidth bill**, and the strongest candidates are the ones who compute that $55M/year number early and spend the deep dive earning it back. Start with the questions, let the numbers pick your components, and go deep exactly where the problem is actually hard.

