Want it harder? Hide the model answer and work each phase in a scratchpad against its time budget, then reveal and score yourself. Or simply read on: the checkpoints below work either way.
How Do You Turn Twenty Million Uploads a Day Into Playable Video, and Count Every View Honestly?
It is 6 pm in Mumbai and Asha has just finished editing a 12-minute cooking video on her phone. She taps "Upload" on a crowded local train. The file is 1.8 GB. Two stations later the train goes into a cutting and her phone drops to no signal.
Asha expects three things. The upload carries on where it stopped, not from zero. Her video is watchable a few minutes after the last byte lands, even if the 4K version takes longer. And when her audience starts watching, the view count on her video goes up with every real person, and not with every bot somebody points at it.
Twenty million other people will upload something today too.
- 20 billion+ — videos uploaded since 2005, and 20 million+ uploaded on an average day (YouTube's 20th-birthday post, April 2025)
- 500+ hours — of video uploaded every minute on average (YouTube, April 2021)
- 200 billion+ — daily Shorts views (YouTube CEO Neal Mohan, June 2025; since March 2025 a Shorts view counts every start or replay)
- $60 billion+ — YouTube's 2025 revenue across ads and subscriptions (Alphabet, February 2026)
- 256 GB or 12 hours — the largest single upload, whichever is less (YouTube help centre)
- 20–33× — compute efficiency of YouTube's video transcoding chips over its previous software system (YouTube, April 2021)
For context:
From 24 August 2026 YouTube counts a view the moment a video starts to play, in every format (YouTube help centre, as of October 2026)
YouTube has never published a monthly-user count in its filings, so this quest doesn't use one
Everything not in the list above is an illustrative design target, not YouTube's internal architecture
This is the system we'll design today.
YouTube takes 20 million+ uploads a day and 500+ hours of video a minute, and Shorts alone get 200 billion+ views a day. Uploads resume from the stored offset; the original is kept, split at closed GOPs into chunks transcoded in parallel, low quality first, so a video is watchable in minutes; view events are deduplicated, filtered and added to sharded counters; recommendations narrow billions of videos to hundreds, then rank by expected watch time. The deep dives cover resumable sessions, task graphs and popularity tiers, view counting and two-stage recommendations, then an encoder bug and a three-hour 4K upload at peak.
The Interview Begins#
You're interviewing at a company that runs a video platform. The interviewer pulls up a dashboard: a line climbing through the evening labelled "uploads in progress", and next to it a counter of views ticking too fast to read.
"Netflix-style delivery is a solved problem here; the CDN team owns it. I want the other half: getting video in, turning it into something playable, counting who watched it, and deciding what people watch next."
Creators upload videos from phones, browsers and studio software. Each upload becomes a set of playable versions for every screen and connection. Viewers watch, and every view is counted. The home page and "Up next" recommend what to watch.
Requirements
- Resumable uploads of files up to 256 GB over unreliable connections
- A new video playable in low quality within minutes, higher qualities after
- An honest, public view count that bots can't inflate
- Recommendations chosen from the whole catalogue in about 100 ms
- Survive a video going viral minutes after upload
"Leave out video delivery, live streaming, ads and comments. Upload, processing, views, recommendations."
Phase 1: Requirements Clarification (5 minutes)#
"The upload side and the watch side have very different shapes, so I want numbers for both."
Your Questions#
"How much comes in?"
"YouTube's own figures: more than 20 million videos uploaded on an average day, and more than 500 hours of video every minute. Assume both."
"How big can one upload be, and what arrives?"
"Up to 256 GB or 12 hours, whichever is less. Anything from a 15-second phone clip to a 4K film exported from editing software, in whatever container and codec the creator used."
"How quickly must a new video be watchable?"
"YouTube's help centre says a new video is available 'in low quality first', and that 'a 4K video with a frame rate of 30fps that is 60 minutes long can take up to 4 hours to finish high-resolution processing'. Match the first part and beat the second."
"What counts as a view?"
"From 24 August 2026, YouTube counts a view 'the moment a video begins to play', for Shorts, long videos and live streams alike. Its help centre also says it may 'temporarily slow down, freeze, or change' a count while it removes 'low-quality playbacks', like the same video streamed in several tabs. Design for that."
"And recommendations?"
"Home, Up next and the Shorts feed. YouTube has written that recommendations drive more viewing than subscriptions or search. You have about 100 ms."
Functional Requirements#
- Upload: resumable uploads up to 256 GB / 12 hours from phones, browsers and the API; title, description, thumbnail and privacy set while it uploads
- Processing: validate, transcode into a ladder of resolutions and codecs, generate thumbnails, run policy and copyright checks; low quality first, the rest after
- Watch: a manifest per video listing every ready rendition; segments served by the CDN
- Views: count every play from its first frame, filter low-quality playbacks, show the count publicly, record watch time for creators
- Recommendations: home page, Up next and Shorts feed, personalised per viewer
- Lifecycle: edit metadata, replace thumbnails, make private, delete, take down
Non-Functional Requirements#
Latency (design targets)
- Upload resume after a dropped connection: one round trip, no bytes re-sent that the server already stored
- Last byte to playable in low quality: p95 < 5 minutes for a 10-minute video
- Last byte to 1080p: p95 < 20 minutes; 4K within the hour for an hour of footage
- Recommendations: p99 < 100 ms server time
Correctness
- No upload byte lost once acknowledged; an original is never deleted by the pipeline
- No real view lost; no view counted twice; bots and repeats filtered before a count is final
Scale
- 20 million uploads a day with a 3× evening peak; 200 billion+ Shorts views a day plus long-form
- A single video going from 0 to 50,000 views a second
Phase 2: Back of the Envelope Estimation (5 minutes)#
"Four numbers: how much video arrives, what it costs to store, what it costs to transcode, and how many view events we have to count."
Before the numbers: how much video arrives every second?
20 million uploads a day and 500 hours of video a minute. How many uploads start each second on average? How many seconds of video arrive every second? And what do the two numbers together say about a typical upload?
One strong answer
- Uploads: 20,000,000 ÷ 86,400 s ≈ 231 a second on average; at a 3× evening peak about 700, so design for 1,000 a second.
- Video: 500 hours a minute is 500 × 3,600 s ÷ 60 s = 30,000 seconds of video arriving every second.
- Average length: 500 h × 1,440 minutes a day = 720,000 hours a day, ÷ 20 million uploads ≈ 2.2 minutes.
The two figures are four years apart, so this is rough, but the shape is right: most uploads are short (Shorts pull the average down), while most of the bytes and compute come from the long ones.
Upload Estimation#
| Value | |
|---|---|
| Uploads a day | 20 million |
| Average / design peak | ~231/s / 1,000/s |
| Video arriving | 30,000 s of video every second |
| Average upload length | ~2.2 minutes |
- Ingest bandwidth: 30,000 s/s × 8 Mbps = 240 Gbps on average, ~720 Gbps at a 3× peak
- Originals a day: 240 Gbps = 30 GB/s × 86,400 s ≈ 2.6 PB a day
- Concurrent upload sessions: 1,000 starts a second × an average 10 minutes on a phone uplink = 600,000 sessions open at once
Conclusion: the front door must hold hundreds of thousands of slow, long-lived uploads, so it cannot buffer whole files in memory.
Storage Estimation#
- Renditions: a full ladder in two codecs is roughly the size of the original again: ~2.6 PB a day
- New data: originals + renditions ≈ 5.2 PB a day; with erasure coding at 1.5× overhead, ~7.8 PB a day, ~2.8 EB a year
- The long tail: if most videos get only a basic ladder until they prove popular (half the rendition bytes), renditions drop to ~1.3 PB a day and the year to ~2.1 EB
Conclusion: the original is kept forever, because it is the only thing you can re-encode from when codecs improve. Renditions are a cache you can rebuild.
Transcoding Estimation#
- A measured reference: Meta's engineers published that a 23-second clip took 86.17 CPU-seconds to transcode to 720p with x264, about 3.7 CPU-seconds per second of video for one rendition
- One ladder (144p to 1080p in one codec) ≈ 4× a 720p rendition ≈ 15 CPU-seconds per second of video
- Fleet, one codec, software: 30,000 s/s × 15 = 450,000 CPU cores busy around the clock; ~1.35 million at the 3× peak
- With transcoding chips at YouTube's reported 20–33× efficiency (take 25×): ~18,000 core-equivalents on average
Conclusion: software transcoding at this volume is a data-centre-sized bill before a second codec is added. Every rendition you don't make for a video nobody watches is real money.
Views and Recommendations Estimation#
- Shorts views: 200 billion ÷ 86,400 s ≈ 2.3 million a second
- All view starts: assume ~3 million a second with long-form; 2× peak = 6 million a second
- Events: start, a watch-time heartbeat, end ≈ 3 per view × 150 B: 18 million events/s, ~2.7 GB/s (22 Gbps) at peak; ~117 TB a day
- A viral video:50,000 views a second on one video ID
- Recommendations: one request per 5 view starts ≈ 1.2 million a second at peak
- Scoring 500 candidates each = 600 million model evaluations a second
- Scoring all 20 billion videos for one request is impossible in 100 ms
Conclusion: views are a firehose with a hot-key problem; recommendations need a cheap first stage that never looks at the whole catalogue.
Key Metrics Summary#
| Metric | Value |
|---|---|
| Uploads (average / design peak) | ~231/s / 1,000/s |
| Upload sessions open at once | ~600,000 |
| Video arriving | 30,000 s of video every second |
| Originals ingested | ~2.6 PB a day, ~720 Gbps at peak |
| Software transcoding (one codec) | ~450,000 cores on average |
| View starts at peak | ~6 million/s, ~18 million events/s |
| Recommendation requests at peak | ~1.2 million/s |
Requirements and estimation
Q1/3 20 million uploads a day, and 720,000 hours of video a day. What is the average upload length?
Answer & explanation
Answer: B
720,000 h ÷ 20 M = 0.036 h ≈ 2.2 minutes. Short videos dominate the count; long ones dominate the bytes.
Q2/3 Why is the original upload kept forever, even after every rendition is made?
Answer & explanation
Answer: C
Renditions are lossy outputs. A better codec, or a popular video getting a better ladder, needs the original.
Q3/3 Why can't the recommendation service score every video for every request?
Answer & explanation
Answer: D
A cheap candidate stage narrows tens of billions to hundreds; only those are scored by the expensive model.
Phase 3: High-Level Design (10 minutes)#
Sketch it first: Asha's video, from her phone to the first view
Trace Asha's upload from her phone to the first viewer, and that viewer's play into the count on the page. Where are the bytes stored first? What starts processing, and what does the first viewer actually get? Where does the view go?
One strong answer
- Upload: her app opens a resumable session and sends chunks to an upload front end, which writes them straight into object storage as the original. The front end only keeps track of how many bytes are safely stored.
- Processing: the "upload complete" event starts a processing job: probe the file, split it into chunks, transcode chunks in parallel into the ladder, low-quality renditions first, then package segments and a manifest.
- First viewer: the watch API returns a manifest listing only the renditions that are ready, so the first viewer may get 360p while 1080p is still being made. Segments come from the CDN.
- The view: the player sends a view event when playback starts. It goes into an event stream; a counting pipeline removes duplicates and low-quality playbacks and adds the rest to the video's count.
- Discovery: the same events feed the recommendation models, which pick the next video from candidates, not from the whole catalogue.
"Five paths: bytes in, processing, watch, counting, recommending. Only the first two touch the video itself; the other three are about events."
System Architecture#
%% title: Video platform architecture: upload, processing, views, recommendations
flowchart TD
subgraph L1["Layer 1 · Clients"]
direction LR
CR@{ icon: "sd:phone", label: "Creators<br/>phone, browser, studio apps" }
VW@{ icon: "sd:users", label: "Viewers<br/>web, apps, TVs" }
end
subgraph L2["Layer 2 · Edge"]
direction LR
UP@{ icon: "sd:gateway", label: "Upload front ends<br/>resumable sessions" }
API@{ icon: "sd:api", label: "Watch and feed API" }
CDN@{ icon: "sd:cdn", label: "CDN<br/>segments and manifests" }
end
subgraph L3["Layer 3 · Processing"]
direction LR
ORCH["Processing orchestrator<br/>one task graph per video"]:::hot
TR@{ icon: "sd:cpu", label: "Transcode pool<br/>chunks in parallel" }
CHK@{ icon: "sd:shield", label: "Checks<br/>policy, copyright, thumbnails" }
end
subgraph L4["Layer 4 · Storage"]
direction LR
ORIG@{ icon: "sd:object-storage", label: "Originals<br/>kept forever" }
REND@{ icon: "sd:object-storage", label: "Renditions<br/>a rebuildable cache" }
META@{ icon: "tech:mysql", label: "Video metadata<br/>sharded MySQL" }
end
subgraph L5["Layer 5 · Views and discovery"]
direction LR
EV@{ icon: "tech:kafka", label: "Kafka · view events" }
CNT["View counter<br/>dedupe, filter, sharded counts"]:::core
REC["Recommendations<br/>candidates, then ranking"]:::core
end
CR ==>|"chunks, resumable"| UP
UP ==> ORIG
ORIG -.->|"upload complete"| ORCH
ORCH --> TR
ORCH --> CHK
TR ==> REND
ORCH -->|"renditions ready"| META
REND --> CDN
VW ==> API
VW ==>|"segments"| CDN
API --> META
API --> REC
VW -.->|"view and watch-time events"| EV
EV --> CNT
EV --> REC
class CR,VW client
class UP,API,CDN edge
class TR,CHK service
class ORIG,REND,META store
class EV queueKEY INSIGHTS:
- The original is the source of truth; every rendition can be rebuilt from it, so processing can be retried, reordered and redone
- Processing is a task graph, not a function call: hundreds of chunks per video, scheduled across a shared pool with priorities
- The watch API only lists what is ready, so a video goes live in low quality and gains renditions as they finish
- Views and recommendations are event pipelines fed from the same stream
Data Flow: One Upload, From the Train to the First View#
%% title: Asha's upload, from her phone to the first counted view sequenceDiagram autonumber participant A as Asha's app participant U as Upload front end participant S as Originals store participant O as Orchestrator participant T as Transcode pool participant V as Viewer A->>U: Start session, 1.8 GB, title, privacy U-->>A: Session URI A->>U: Chunks of 8 MB U->>S: Write part, then record the stored offset Note over A,U: train enters a cutting, connection drops at 1.1 GB A->>U: Status? bytes */1,800,000,000 U-->>A: 308 · Range bytes=0-1,099,999,999 A->>U: Remaining 700 MB from byte 1,100,000,000 U->>S: Last part, checksum matches S-)O: Upload complete, video v_7Hq2 O->>T: 180 chunks · 144p to 480p first T-->>O: Low ladder ready O->>O: Publish manifest with 144p to 480p V->>V: Plays at 480p, view event sent O->>T: 720p, 1080p, newer codec
MEANWHILE, IN PARALLEL:
- Policy and copyright checks run against the original as soon as it is complete; a video that fails them never gets a public manifest
- Thumbnails are generated from the first chunks, so Asha can choose one while the HD renditions are still being made
High-level design
Q1/3 Asha's connection drops after 1.1 GB of a 1.8 GB upload. What does the server need to know to resume?
Answer & explanation
Answer: B
The client asks for the session's status and resumes from the server's stored offset. Nothing already stored is sent again.
Q2/3 A viewer opens the video before 1080p is ready. What do they get?
Answer & explanation
Answer: C
The video goes live in low quality and gains renditions as they finish; the player switches up as they appear.
Q3/3 Why are views sent as events to a stream rather than written straight into the video's row?
Answer & explanation
Answer: B
The stream absorbs the firehose; the counting pipeline cleans and aggregates it before touching the count.
Phase 4: Deep Dives (20 minutes)#
Deep Dive 1: Resumable Uploads (Week 2 Concepts: Idempotency, Timeouts)#
"An upload from a phone is a long transfer over a network that drops all the time. The protocol has to make every retry harmless."
This builds on Week 2, Day 2 · Idempotency in Practice: the same request sent twice must have the effect of one.
The cutting
Asha's 1.8 GB upload drops at 1.1 GB. The phone had sent 1,104 MB; the front end had received 1,102 MB; 1,100 MB were written to storage. Which number does the server tell her phone when it reconnects, and what goes wrong if it picks either of the others?
One strong answer
- The stored number: 1,100 MB. Only bytes durably written count as received.
- If it said 1,104 MB (what the phone sent): 4 MB never arrived, and the file has a hole.
- If it said 1,102 MB (what the front end received): 2 MB were in the front end's memory when it, or the connection, went away. Same hole.
- Resending is harmless: the phone sends from byte 1,100,000,000. If it resends bytes the server already holds, the server stores nothing and answers with its offset again.
- Checking at the end: a checksum of the whole file, sent by the client, catches anything the offsets missed.
The Solution:
YouTube's public API documents exactly this shape: a request starts a session and returns its URI in a Location header; the client sends the file in chunks that "must be a multiple of 256 KB"; to check progress it sends Content-Range: bytes */<size>; and an incomplete upload answers 308 Resume Incomplete with a Range header that says "how many bytes of the file have already been successfully uploaded".
%% title: The life of one upload session stateDiagram-v2 [*] --> OPEN: session created, URI returned OPEN --> RECEIVING: first chunk RECEIVING --> RECEIVING: chunk stored, offset advanced RECEIVING --> PAUSED: connection lost PAUSED --> RECEIVING: status query, resume from offset PAUSED --> EXPIRED: no activity for days RECEIVING --> COMPLETE: last byte stored, checksum ok RECEIVING --> FAILED: checksum mismatch COMPLETE --> [*] EXPIRED --> [*] FAILED --> [*] OPEN: OPEN · metadata saved, no bytes yet PAUSED: PAUSED · stored parts kept COMPLETE: COMPLETE · original sealed, processing starts class RECEIVING core class COMPLETE ok class FAILED bad class EXPIRED bad
- The front end stores parts, not files. Each chunk becomes a part in object storage (a multipart upload); the session row records which parts are sealed. Any front end can serve the next chunk, because the state lives in the session table, not in a socket.
- The offset only moves after the part is durable. That single rule makes resume correct.
- Sessions outlive connections, not forever. The API notes that each session URI "has a finite lifetime and eventually expires"; parts of expired sessions are garbage-collected.
- Metadata goes in first. Title, privacy and description are saved when the session opens, so the video exists (as a draft) even while bytes are still arriving.
# upload/session.py
"""
A resumable upload session (illustrative). The client sends chunks at any
offset; the server only advances `stored` after a part is durably written,
and answers status queries with that number. Resending bytes it already
has is harmless.
"""
import hashlib
from dataclasses import dataclass, field
from typing import Dict, Optional
CHUNK = 256 * 1024 # chunks are multiples of 256 KB, except the last
@dataclass
class Session:
session_id: str
video_id: str
size: int # declared total size in bytes
stored: int = 0 # bytes durably written, from offset 0
parts: Dict[int, str] = field(default_factory=dict) # part number -> etag
state: str = "OPEN"
class UploadFrontEnd:
def __init__(self, sessions, blobs, events):
self.sessions, self.blobs, self.events = sessions, blobs, events
def put(self, sid: str, offset: int, data: bytes) -> dict:
s: Session = self.sessions.get(sid)
if s.state == "EXPIRED":
return {"status": 410}
if s.state in ("COMPLETE", "FAILED"):
return {"status": 200 if s.state == "COMPLETE" else 400}
if offset != s.stored:
return self.status(sid) # a gap, or bytes we already have: say where to resume
last = offset + len(data) == s.size
if len(data) % CHUNK and not last:
return {"status": 400, "error": "chunk must be a multiple of 256 KB"}
part_no = len(s.parts) + 1
etag = self.blobs.put_part(s.video_id, part_no, data) # durable before we move on
s.parts[part_no] = etag
s.stored += len(data)
s.state = "RECEIVING"
self.sessions.save(s) # conditional on the previous `stored`
return self.status(sid)
def status(self, sid: str) -> dict:
s: Session = self.sessions.get(sid)
if s.stored < s.size:
return {"status": 308, "range": f"bytes=0-{s.stored - 1}" if s.stored else None}
return {"status": 200}
def finish(self, sid: str, client_sha256: Optional[str]) -> dict:
s: Session = self.sessions.get(sid)
if s.stored != s.size:
return self.status(sid)
sha = self.blobs.complete(s.video_id, s.parts) # stitches parts, returns digest
if client_sha256 and client_sha256 != sha:
s.state = "FAILED"
self.sessions.save(s)
return {"status": 400, "error": "checksum mismatch"}
s.state = "COMPLETE"
self.sessions.save(s)
self.events.publish("upload.complete", {"video_id": s.video_id, "sha256": sha})
return {"status": 200, "video_id": s.video_id}
def sha256_of(path: str) -> str:
h = hashlib.sha256()
with open(path, "rb") as f:
for block in iter(lambda: f.read(1 << 20), b""):
h.update(block)
return h.hexdigest()Design choices worth saying out loud:
- Chunk size is a trade-off. Bigger chunks mean fewer requests but more bytes resent after a drop. 8 MB on a phone, larger from a studio.
- Upload close to the creator, store the original once. The front end nearest Asha takes the bytes; the original is replicated by the storage layer, not by the client uploading twice.
- Don't start transcoding on a half-uploaded MP4. Many phone files put the index (the
moovbox) at the end, so the file can't be read until the last byte. A client that records fragmented MP4 lets processing start early; otherwise the pipeline starts on "upload complete".
Deep Dive 2: The Processing Pipeline as a Task Graph (Week 3 Concepts: Queues, Backpressure, Dead Letters)#
"Transcoding a long video on one machine takes hours. The trick is to make one video into hundreds of small, independent, retryable jobs, and to decide which of them matter first."
This builds on Week 3, Day 3 · Backpressure and Flow Control and Week 3, Day 4 · Dead Letters and Poison Pills.
A two-hour 4K film
A creator uploads a two-hour 4K film. One worker would take many hours to produce every rendition. How do you get a watchable version in minutes, every rendition within about an hour, and still not let this one film starve the 1,000 other uploads arriving each second?
One strong answer
- Split by time: cut the video at closed GOP boundaries (points where a chunk can be decoded without its neighbours) into ~4-second chunks: 7,200 s ÷ 4 = 1,800 chunks, each an independent job.
- Decode once, encode many: each job decodes its chunk once and encodes all the renditions of one tier from it.
- Low ladder first: the 144p–480p tier is queued at high priority. 1,800 jobs across 1,800 workers finish in about the time of one chunk, a minute or two.
- Then the rest: 720p–1080p at normal priority, 4K and newer codecs lower. Each tier publishes when its last chunk is done.
- Fairness: cap how many workers one video may hold at once (say 2,000), so a film is fast but other uploads still get workers.
- Assemble: stitch chunk outputs per rendition, check the duration matches the original, write the manifest.
The Challenge:
- A two-hour film holds one worker for hours, and its first viewer waits for every rendition
- A crash at 95% starts the whole video over
- One corrupt frame (a "poison" file) crashes the worker again on every retry
- A burst of evening uploads queues behind a few long films
The Solution:
%% title: One video's processing task graph
flowchart TD
IN(["upload.complete<br/>v_7Hq2, 12 min, 1080p source"]):::client
PROBE["Probe<br/>codec, length, rotation, GOPs"]:::service
SPLIT["Split at closed GOPs<br/>180 chunks of ~4 s"]:::core
subgraph T1["Tier 1 · high priority"]
direction LR
LOW["Per chunk: decode once<br/>encode 144p to 480p"]:::hot
end
subgraph T2["Tier 2 · normal priority"]
direction LR
HD["Per chunk: 720p, 1080p"]:::core
end
subgraph T3["Tier 3 · by popularity, low priority"]
direction LR
ADV["Newer codec, 4K<br/>only if watched enough"]:::queue
end
CHECKS["Policy, copyright,<br/>thumbnails"]:::service
ASM["Assemble per rendition<br/>duration must match"]:::service
PUB(["Manifest updated<br/>tier by tier"]):::ok
IN --> PROBE --> SPLIT
PROBE --> CHECKS
SPLIT --> LOW --> ASM
SPLIT --> HD --> ASM
SPLIT -.-> ADV -.-> ASM
CHECKS -->|"must pass before public"| PUB
ASM --> PUB- Every task has a deterministic output key:
(video, chunk, tier, encoder version). A retried task overwrites the same object with the same bytes, so retries are idempotent and a crash costs one chunk, not one video. - Three queues, not one: tier 1 is latency-sensitive and never waits behind tiers 2 and 3; tier 3 fills the troughs of the daily cycle.
- Poison chunks go to a dead-letter queue after a few failures, with the error. Retrying a corrupt chunk forever wastes the pool; one bad chunk marks that rendition failed while the others publish.
- Integrity checks are cheap and essential: the Argos paper describes checks such as "video length must match the input", because one faulty accelerator could corrupt many videos.
# processing/plan.py
"""
Plan one video's processing as a task graph (illustrative). Each task is a
(chunk, tier) pair with a deterministic output key, so retries overwrite the
same object. Tiers are published independently: the video goes live with
tier 1 and gains tiers as their last chunk finishes.
"""
from dataclasses import dataclass
from typing import List
TIERS = {
1: {"renditions": ["144p", "240p", "360p", "480p"], "priority": "high"},
2: {"renditions": ["720p", "1080p"], "priority": "normal"},
3: {"renditions": ["1440p", "2160p", "newer-codec ladder"], "priority": "low"},
}
CHUNK_S = 4.0
MAX_WORKERS_PER_VIDEO = 2_000
ENCODER = "enc-2026.10"
@dataclass(frozen=True)
class Task:
video_id: str
chunk: int
start_s: float
end_s: float
tier: int
@property
def output_key(self) -> str: # same task, same key: retries are harmless
return f"{self.video_id}/{ENCODER}/t{self.tier}/{self.chunk:05d}"
def tiers_for(height: int, popularity: str) -> List[int]:
tiers = [1]
if height >= 720:
tiers.append(2)
if height >= 1440 or popularity in ("hot", "warm"):
tiers.append(3) # long-tail videos skip it until they earn it
return tiers
def plan(video_id: str, duration_s: float, gop_starts: List[float], height: int, popularity: str):
cuts, last = [0.0], 0.0
for g in gop_starts: # cut only where a chunk decodes on its own
if g - last >= CHUNK_S:
cuts.append(g)
last = g
cuts.append(duration_s)
chunks = list(zip(cuts, cuts[1:]))
tasks = [Task(video_id, i, a, b, tier)
for tier in tiers_for(height, popularity)
for i, (a, b) in enumerate(chunks)]
return {"tasks": tasks, "chunks": len(chunks), "cap": MAX_WORKERS_PER_VIDEO}
def on_task_done(state, task: Task) -> None:
done = state.mark_done(task) # returns chunks finished for this tier
if done == state.chunk_count(task.video_id):
state.enqueue_assemble(task.video_id, task.tier) # stitch, check duration, write manifestNot every video deserves every rendition#
The Argos paper describes video popularity as "a stretched power law distribution" with three buckets: the very popular videos that make up most watch time (worth extra processing to cut bandwidth), modestly watched videos, and the long tail, "the majority of videos", where it makes sense "to minimize storage and transcoding costs while maintaining playability". It adds that "old videos can increase in popularity and may need to be reprocessed".
Everything, for everyone
- Every rung in every codec for every upload
- Most of the compute goes to videos nobody watches twice
- Storage roughly doubles the originals
Minimum, forever
- Only a basic ladder, never upgraded
- Cheap, but popular videos waste bandwidth on an old codec
- Viewers on slow networks get worse quality
Earn your renditions
- Basic ladder now; newer codecs and 4K once views cross a threshold
- Re-encode from the original when a long-tail video takes off
- Bandwidth saved where watch time is, compute saved where it isn't
Uploads and processing
Q1/4 When may the upload front end advance a session's stored offset?
Answer & explanation
Answer: C
Only durable bytes count. Advancing earlier leaves a hole if the front end or connection fails.
Q2/4 Why cut chunks only at closed GOP boundaries?
Answer & explanation
Answer: B
A closed group of pictures doesn't reference frames outside itself, so chunks can be transcoded in parallel and retried alone.
Q3/4 A transcode task for chunk 112 crashes and is retried on another worker. What makes this safe?
Answer & explanation
Answer: D
(video, chunk, tier, encoder version) names the output; the retry produces the same bytes in the same place.
Q4/4 Which renditions should a brand-new long-tail video get?
Answer & explanation
Answer: B
Compute and storage follow watch time; the original lets you upgrade a video that later takes off.
Deep Dive 3: Counting Views (Week 8 Concepts: Event Ingestion, Late Data; Week 1: Hot Keys)#
"A view count looks like one integer. It is really a firehose of events, a fraud filter and a hot-key problem, behind a number millions of people refresh."
This builds on Week 8, Day 1 · Event Ingestion at Scale and Week 1, Day 4 · Hot Keys and Skew.
The premiere
A music video premieres. Within a minute it gets 50,000 real view starts a second. A bot farm adds 5,000 a second from 200 addresses, each replaying the video in many tabs. Some players retry their view event when the network blips. How do you count it, and what does the public counter show in the first hour?
One strong answer
- Not one row: 50,000 increments a second on one key would melt any single database row. Partition events by a per-playback
view_id, pre-aggregate per video in one-second windows in each stream worker, and add the window totals to a counter split into, say, 64 shards. - Retries: each playback has one
view_id; a duplicate in the dedupe window is dropped (Week 2's idempotency keys). - Bots: score each view on signals such as views per address and device, many tabs at once, and playback that never progresses. YouTube's help centre names "several devices" and "several windows and tabs" as low-quality playbacks.
- Two numbers: a fast provisional count for the creator's dashboard and a verified public count that only grows by views the filter has cleared. When the two disagree sharply, the public number can rise slowly or pause while the filter catches up, which YouTube says it may do.
- Corrections are allowed: views later judged invalid are subtracted, so the count is eventually right rather than instantly right.
The Challenge:
UPDATE videos SET views = views + 1 fails- Hot key: a viral video concentrates tens of thousands of writes a second on one row
- Duplicates: player retries and reconnects double-count
- Fraud: anyone can inflate a number that drives fame, money and recommendations
- Overflow: YouTube once joked that Gangnam Style passed what a signed 32-bit integer can hold (2,147,483,647); it had already moved the counter to 64 bits
The Solution:
%% title: From a play to the public count
flowchart TD
P(["Player<br/>view_id per playback"]):::client
ED@{ icon: "sd:gateway", label: "Event edge<br/>auth, rate limits" }
K@{ icon: "tech:kafka", label: "Kafka · view events<br/>partitioned by view_id" }
AGG@{ icon: "tech:flink", label: "Flink · dedupe by view_id,<br/>1-second totals per video" }
SPAM["Quality filter<br/>address, device, tabs, progress"]:::core
SH@{ icon: "tech:redis", label: "Redis · provisional count<br/>64 shards per hot video" }
VER[("Verified counts<br/>public, corrections applied")]:::store
DASH(["Creator analytics<br/>watch time, retention"]):::ok
P --> ED --> K --> AGG
AGG -->|"provisional"| SH
AGG --> SPAM
SPAM -->|"cleared views"| VER
SPAM -.->|"invalid: subtract later"| VER
VER --> DASH
class ED edge
class K queue
class AGG service
class SH cache- Partition by playback, aggregate by video. Partitioning the stream by
video_idwould put a viral video on one partition; partitioning byview_idspreads it, and each worker sends one small total per video per second. - Shard only the hot ones. A video above ~1,000 views a second gets 64 counter shards; reads sum them (cached for a second). Most videos keep one row.
- The public number is a read model built from verified counts, cached at the edge for a few seconds; nobody reads the raw shards.
# views/counter.py
"""
View counting (illustrative). Events are deduplicated by view_id within a
window, aggregated per video per second, and added to a sharded counter.
The public count only advances by views the quality filter has cleared.
"""
import time
import zlib
from collections import defaultdict
from typing import Dict
DEDUPE_TTL_S = 6 * 3600 # a retried event within 6 hours is a duplicate
HOT_SHARDS = 64
HOT_THRESHOLD = 1_000 # views/s above which a video's counter is sharded
class WindowAggregator:
"""One stream worker: its partitions are keyed by view_id, not video_id."""
def __init__(self, seen, counters):
self.seen, self.counters = seen, counters # seen: TTL set; counters: sharded store
self.window: Dict[str, int] = defaultdict(int)
self.window_start = time.monotonic()
def on_event(self, ev: dict) -> None:
if ev["type"] != "view_start":
return
if not self.seen.add_if_absent(ev["view_id"], ttl=DEDUPE_TTL_S):
return # player retry: already counted
self.window[ev["video_id"]] += 1
if time.monotonic() - self.window_start >= 1.0:
self.flush()
def flush(self) -> None:
for video_id, n in self.window.items():
self.counters.add(video_id, n) # one write per video per second per worker
self.window.clear()
self.window_start = time.monotonic()
class ShardedCounter:
def __init__(self, kv, rates):
self.kv, self.rates = kv, rates
def add(self, video_id: str, n: int) -> None:
if self.rates.views_per_s(video_id) < HOT_THRESHOLD:
self.kv.incr(f"v:{video_id}", n)
return
shard = zlib.crc32(f"{video_id}:{time.time_ns()}".encode()) % HOT_SHARDS
self.kv.incr(f"v:{video_id}:{shard}", n) # 64 rows share the heat
def read(self, video_id: str) -> int:
keys = [f"v:{video_id}"] + [f"v:{video_id}:{s}" for s in range(HOT_SHARDS)]
return sum(self.kv.mget(keys, default=0)) # 64-bit integers, never 32Provisional, verified and engaged#
- Started: the first frame plays and the player sends
view_startwith a freshview_id; from 24 August 2026 this is what YouTube counts as a view - Provisional: deduplicated and added within a second or two; the creator sees it in live analytics
- Verified: the quality filter clears it (minutes to hours for suspicious traffic); the public count includes it
- Engaged: watched beyond the first few seconds; YouTube keeps this as a separate "engaged views" metric for creators
- Corrected: if later judged invalid, it is subtracted from the verified count
Deep Dive 4: Recommendations in 100 ms (Week 7 Concepts: Retrieval and Relevance; Week 4: Caching)#
"Recommendations are search without a query: the viewer's history is the query and the catalogue is the index."
This builds on Week 7, Day 3 · Query Processing and Relevance: retrieve broadly and cheaply, then rank precisely.
Twenty out of twenty billion
Rohan opens the app. There are more than 20 billion videos and you have about 100 ms to show him 20. Scoring one video with your best model takes about 0.2 ms of compute. How do you split the work, and what should the model predict?
One strong answer
- Scoring everything: 20 billion × 0.2 ms is about 46 days of compute for one request. Impossible.
- Stage 1, candidate generation: cheap sources narrow the catalogue to a few hundred: nearest neighbours of Rohan's embedding in a precomputed index of video embeddings, new uploads from channels he subscribes to, videos often watched after what he just watched.
- Stage 2, ranking: the expensive model scores those ~500 candidates with hundreds of features each: 500 × 0.2 ms = 100 ms of compute, parallelised across cores to fit in ~30 ms.
- What to predict: not clicks, which reward clickbait. YouTube's 2016 paper ranks by expected watch time per impression; its 2021 post adds survey-based satisfaction ("valued watchtime"), likes, dislikes and shares.
- Then rules: remove what he has watched, cap one channel's share, demote borderline content, and mix in fresh videos.
What YouTube has published: the 2016 paper describes "the classic two-stage information retrieval dichotomy": a candidate generation network that takes events from the viewer's history and retrieves "a small subset (hundreds) of videos from a large corpus", then a ranking network that assigns each a score; at serving time "an approximate nearest neighbor lookup is performed to generate hundreds of candidate video" recommendations. Ranking optimises "expected watch time per impression" using weighted logistic regression, because ranking by click-through rate "promotes deceptive videos that the user does not complete". An "example age" feature lets the model learn that viewers prefer fresh videos. YouTube's 2021 post on the system lists its signals as "clicks, watchtime, survey responses, sharing, likes, and dislikes", and says that when it first weighted watch time in 2012, views dropped by 20%.
%% title: Serving one recommendation request
flowchart TD
REQ(["Rohan opens Home<br/>user id, device, time"]):::client
FS@{ icon: "sd:database", label: "Feature store<br/>recent watches, searches" }
subgraph CG["Stage 1 · candidates, ~5 ms each, in parallel"]
direction LR
ANN@{ icon: "sd:index", label: "ANN index<br/>video embeddings" }
SUB["Subscriptions<br/>new uploads"]:::service
COW["Co-watch<br/>after what he just watched"]:::service
FRESH["Fresh and trending<br/>in his language"]:::service
end
MERGE["Merge, dedupe<br/>~500 candidates"]:::core
RANK@{ icon: "sd:ml", label: "Ranking model<br/>expected watch time, satisfaction" }
RULES["Rules<br/>watched, diversity, policy"]:::core
OUT(["20 videos<br/>logged as impressions"]):::ok
REQ --> FS --> CG
ANN --> MERGE
SUB --> MERGE
COW --> MERGE
FRESH --> MERGE
MERGE --> RANK --> RULES --> OUT
class FS store
class ANN cache
class RANK service# recs/serve.py
"""
Two-stage recommendation serving (illustrative). Candidate sources are cheap
and run in parallel; only their union is scored by the ranking model. The
response is logged as impressions, which become training labels when the
view events come back.
"""
import asyncio
from typing import List
SOURCES_TIMEOUT_S = 0.020
MAX_CANDIDATES = 500
SHOW = 20
MAX_PER_CHANNEL = 2
async def recommend(user_id: str, ctx: dict, deps) -> List[dict]:
feats = await deps.features.get(user_id) # recent watches, searches, locale
user_vec = deps.user_tower(feats, ctx) # one embedding per request
sources = [
deps.ann.search(user_vec, k=300), # nearest video embeddings
deps.subscriptions.new_uploads(user_id, k=100),
deps.cowatch.after(feats["last_watched"], k=100),
deps.fresh.trending(feats["language"], k=50),
]
done = await asyncio.wait_for(asyncio.gather(*sources, return_exceptions=True),
timeout=SOURCES_TIMEOUT_S)
seen, cands = set(feats["watched_recently"]), []
for result in done:
if isinstance(result, Exception):
continue # a slow source is skipped, not awaited
for v in result:
if v["id"] not in seen:
seen.add(v["id"])
cands.append(v)
cands = cands[:MAX_CANDIDATES]
scores = await deps.ranker.score(feats, ctx, cands) # batched on accelerators
ranked = sorted(zip(cands, scores), key=lambda p: p[1]["value"], reverse=True)
out, per_channel = [], {}
for v, s in ranked:
if s["borderline"] or per_channel.get(v["channel"], 0) >= MAX_PER_CHANNEL:
continue
per_channel[v["channel"]] = per_channel.get(v["channel"], 0) + 1
out.append({"video_id": v["id"], "score": s["value"]})
if len(out) == SHOW:
break
deps.impressions.log(user_id, ctx, out) # labels arrive later as view events
return out
def value(p_click: float, expected_watch_s: float, p_satisfied: float) -> float:
"""What the ranker optimises: watch time a viewer would value, not raw clicks."""
return p_click * expected_watch_s * (0.5 + p_satisfied)Clicks
- Easy to measure and fast to learn
- Rewards misleading thumbnails: the viewer clicks, then leaves
- YouTube's 2016 paper calls this out directly
Watch time
- Rewards videos people actually watch
- Long videos and endless sessions can win over good ones
- YouTube's 2012 switch cut views by 20% at first
Valued watch time
- Watch time weighted by predicted satisfaction from surveys
- Adds likes, dislikes and shares as signals
- Harder to measure; needs survey data and careful models
Caching, carefully: the home page for a returning viewer can be precomputed in the background and refreshed on open, but Up next depends on the video just watched and must be computed live. A new upload has no co-watch history; the subscriptions and fresh sources, plus the example-age feature, give it a chance to be seen.
Views and recommendations
Q1/4 A viral video gets 50,000 view events a second. Why partition the stream by view_id instead of video_id?
Answer & explanation
Answer: B
Spreading by playback lets every worker take a share; each sends one total per video per second to the counter.
Q2/4 A player resends a view_start after a network blip. What stops it counting twice?
Answer & explanation
Answer: C
One playback, one view_id; a second copy inside the window is dropped.
Q3/4 Why does the ranking model predict expected watch time rather than click probability?
Answer & explanation
Answer: B
YouTube's 2016 paper says click-through ranking promotes deceptive videos; its 2021 post adds satisfaction surveys on top of watch time.
Q4/4 How does a video uploaded ten minutes ago get recommended at all?
Answer & explanation
Answer: D
Candidate sources that don't need watch history, plus a freshness feature, solve the cold start.
Phase 5: Scaling and Edge Cases (5 minutes)#
"Walk me through a video's first day, and then the cases that bite."
A Video's First Day#
- 6:00 PM · Upload starts
- Session opened, metadata saved as a draft; 8 MB chunks over a phone uplink
- 6:14 PM · Upload complete
- One reconnect in a cutting, resumed from the stored offset; checksum matches; processing starts near Mumbai
- 6:16 PM · Live in low quality
- 180 chunks through tier 1; policy checks passed; the manifest lists 144p to 480p
- 6:24 PM · HD ready
- 720p and 1080p published; players switch up on their next segment
- 9:00 PM · It takes off
- 3,000 views a second; the counter shards; the video crosses the popularity threshold and gets the newer codec ladder at low priority
- Next day · Long-tail or hit
- Recommendations keep it in Up next for viewers of similar videos; verified counts catch up as the filter clears traffic
Edge Cases#
Edge case 1: The same file uploaded twice
Problem: A creator's app crashes and they upload the identical 4 GB file again.
Solution: The new session is a new video: two uploads can be meant as two videos. The storage layer can still deduplicate identical bytes by content hash under the hood, invisible to the creator.
Edge case 2: A takedown after the video spread
Problem: A video must come down: the creator deletes it, or a court orders it.
Solution: Flip the metadata to unavailable first (the watch API stops issuing manifests), then purge CDN copies, remove it from candidate indexes, and delete renditions. Keep the original under legal hold if required, otherwise delete it on a schedule.
Edge case 3: An old video goes viral
Problem: A 2014 clip with only a basic ladder suddenly gets millions of views.
Solution: Its view rate crosses the popularity threshold and the orchestrator queues tier 3 from the original, as the Argos paper says old videos "may need to be reprocessed". Viewers get better quality within the hour, and bandwidth drops.
Edge case 4: An encoder bug
Problem: A new encoder version produced a green flash in 0.1% of chunks for a day.
Solution: The encoder version is in every output key, so the affected renditions can be found exactly and re-encoded from the originals, newest and most-watched first.
Scaling and edge cases
Q1/3 A video must be taken down. What should happen first?
Answer & explanation
Answer: A
Stopping new playbacks is instant and complete; purges and deletions follow.
Q2/3 A long-tail video from years ago suddenly goes viral. What makes upgrading it possible?
Answer & explanation
Answer: C
Renditions are rebuildable from the original whenever a video earns better treatment.
Q3/3 An encoder bug corrupted some chunks for a day. How do you find exactly which renditions to redo?
Answer & explanation
Answer: D
Putting the encoder version in the output key makes the blast radius a query.
Phase 6: Monitoring and Operations#
"Four questions for the dashboard: are uploads getting in, how long until they're watchable, is the count honest, and are recommendations fast and good?"
Monitoring Dashboard#
Uploads
| Metric | Now |
|---|---|
| Upload sessions open | 612,000 |
| Uploads completed, last minute | 41,300 |
| Resumes per completed upload | 0.7 |
| Checksum failures | 0.003% |
Processing
- Last byte to low quality, p50 / p95: 1.6 / 4.1 min; to 1080p p95: 14 min
- Tier 1 queue: 2 s oldest task; tier 3 queue: 3.2 h (filling tonight's trough)
- Dead-lettered chunks, last hour: 118 (0.0004%)
Views
- View events: 15.8 million/s; stream lag p99 1.4 s
- Videos with sharded counters: 2,310; hottest 41,000 views/s
- Filtered as low quality, last hour: 3.1%
Recommendations
- Latency p50 / p99: 38 / 87 ms; candidate sources timed out: 0.6%
SLOs for the Platform#
SLO 1: Uploads never lose bytes
- Target: 0 acknowledged bytes lost; 99.9% of resumes need no re-sent stored bytes
SLO 2: Watchable fast
- Target: 95% of videos under 15 minutes playable in low quality within 5 minutes of the last byte
SLO 3: Honest counts
- Target: provisional counts within 5 s of the event (p99); verified within 24 h for 99.9% of views
SLO 4: Fast recommendations
- Target: p99 under 100 ms, with at least 18 of 20 slots filled even when one candidate source fails
Reading the dashboard
Q1/3 The tier 3 queue is 3 hours old, but the tier 1 queue is 2 seconds. Is that a problem?
Answer & explanation
Answer: B
Separate priorities let newer codecs and 4K wait while new uploads go live in minutes.
Q2/3 The view stream's lag climbs from 1.4 s to 4 minutes. What do users notice?
Answer & explanation
Answer: C
Counting is asynchronous; playback doesn't wait for it. Lag shows up as stale numbers.
Q3/3 One candidate source times out for 30% of requests. What should the request do?
Answer & explanation
Answer: D
Sources are independent; a missing one costs some variety, not the response.
Final Challenge: A Three-Hour 4K Upload at Peak Hour#
The interviewer leans back. "One more."
"A creator with a huge following uploads a three-hour 4K recording of a live concert at 9 pm, the busiest hour of the evening, and tells everyone it'll be up 'in a few minutes'. Two million people click in the first ten minutes after it goes public. Get it watchable fast, keep the count honest, and tell me what you'd degrade."
- 3 hours of 4K at 60 fps; source bitrate 45 Mbps (assumption)
- Creator's uplink: 200 Mbps
- Chunks of 4 s; on the transcode pool a chunk takes 20 s for tier 1, 120 s for tier 2, 600 s for tier 3 (assumptions)
- Per-video cap: 2,000 workers; the pool is at 85% of capacity at 9 pm
- 2 million viewers in the first ten minutes after publishing
What is fast, what waits, and what never breaks?
Work out the upload time, the number of chunks, the time to each tier, and the view rate. Then decide what you'd hold back.
One strong answer
- The file: 3 h × 3,600 s × 45 Mbps ÷ 8 = 60.75 GB. Upload at 200 Mbps: 60.75 × 8 ÷ 200 = ~2,430 s, about 40 minutes. "A few minutes" was never possible; the upload is the slowest step. Resumability matters more than speed here.
- Chunks: 10,800 s ÷ 4 = 2,700 chunks.
- Tier 1 (144p–480p): 2,700 × 20 s = 54,000 worker-seconds. With 2,000 workers: 27 s, call it a minute with scheduling and assembly. Watchable about a minute after the last byte.
- Tier 2 (720p, 1080p): 2,700 × 120 s = 324,000 worker-seconds ÷ 2,000 = 162 s, about 3 minutes.
- Tier 3 (4K, newer codec): 2,700 × 600 s = 1.62 million worker-seconds ÷ 2,000 = 810 s, about 14 minutes, if the pool can spare 2,000 workers at peak. At 85% load, give tier 3 only what's spare and let it finish after midnight. YouTube's help centre allows hours for this.
- Views: 2 million in 10 minutes = ~3,300 a second on average, spikier at the start. The counter shards automatically above 1,000 a second; the stream is partitioned by view_id, so nothing heats up.
- Recommendations: the video is brand new, so it reaches people through the subscriptions source and freshness, not co-watch.
- Degrade in this order: tier 3 for this and every other video (newer codecs and 4K wait for the trough), then thumbnails beyond the first, then live analytics refresh. Never acknowledged upload bytes, tier 1 for anyone's upload, or the dedupe window.
Final challenge · Quest check
Q1/4 A 60.75 GB file over a 200 Mbps uplink. Roughly how long does the upload take?
Answer & explanation
Answer: B
60.75 GB × 8 = 486 Gb; ÷ 0.2 Gbps ≈ 2,430 s. The upload, not the transcoding, is the slow step.
Q2/4 2,700 chunks at 20 s each for tier 1, with 2,000 workers. About how long is tier 1?
Answer & explanation
Answer: A
54,000 worker-seconds ÷ 2,000 = 27 s, plus scheduling and assembly.
Q3/4 The pool is at 85% at 9 pm. What should wait?
Answer & explanation
Answer: C
Tier 3 improves quality and saves bandwidth later; tier 1 is what makes uploads watchable at all.
Q4/4 Two million viewers in ten minutes. Why doesn't the view counter melt?
Answer & explanation
Answer: C
Partition by playback, write one total per video per second per worker, and shard counters above a threshold.
Interview Conclusion#
"Why not just transcode each upload on one big machine?"
"Because a two-hour film would hold it for hours and a crash would start it over. Chunking at closed GOPs turns one video into thousands of independent, retryable jobs, and priorities let the low ladder go live in a minute while 4K waits for quiet hours."
"Why not count views with a database increment?"
"One viral video is tens of thousands of increments a second on one row, and the number has to resist fraud. Events with a per-playback ID, partitioned by that ID, deduplicated, aggregated per second and filtered, give a count that's fast, honest and eventually right."
"The single most important decision?"
"Keep the original forever and treat everything derived from it, renditions, counts, recommendations, as rebuildable. That's what makes retries, re-encodes, corrections and model changes safe."
Summary: Concepts Applied from 10-Week Course#
- Week 1: Data at scale
- Hot keys — sharded counters for viral videos; streams partitioned by playback, not video
- Week 2: Failure-first design
- Idempotency — resumable uploads, deterministic transcode outputs, view_id deduplication
- Week 3: Messaging and async processing
- Task graphs on a work queue; priorities as backpressure; dead letters for poison chunks
- Week 4: Caching
- Renditions as a rebuildable cache of the original; precomputed home feeds
- Week 7: Search systems
- Candidate generation, then ranking; approximate nearest neighbours
- Week 8: Analytics pipelines
- View-event ingestion, windowed aggregation, provisional versus verified counts
- Week 10: Production readiness
- SLOs on time-to-watchable, count freshness and recommendation latency
Why YouTube Matters#
The write side is enormous
- Most video systems are read-heavy; here, every minute brings hundreds of hours of new video that must be stored forever and transcoded before anyone can watch it
Compute follows popularity
- A power law decides where processing pays off: spend on the videos people watch, keep the long tail cheap, and keep the original so you can change your mind
Counting is a product
- A view count is money, status and a ranking signal at once, so it must be fast, deduplicated and resistant to fraud, and allowed to correct itself
"Keep the original, make everything else rebuildable, and spend compute where the watch time is."
Self-Assessment Checklist#
After studying this case study, you should be able to:
Upload and processing:
- Design a resumable upload protocol and explain why the offset only moves after a durable write
- Split a video at closed GOPs into a task graph with priorities, caps and idempotent outputs
- Explain popularity tiers and why the original is kept forever
Views:
- Build a view pipeline that deduplicates, filters and aggregates before counting
- Shard a hot counter and explain provisional versus verified counts
Recommendations and operations:
- Explain two-stage retrieval and ranking, and size why one stage isn't enough
- Choose what to rank by, and why clicks alone fail
- Estimate upload, transcode and view loads for a big upload at peak, and choose what to degrade
Sources#
YouTube (official):
- Talk
YouTube — Happy 20th birthday, YouTube (20 billion+ videos uploaded, 20 million+ uploaded daily on average, 100 million+ comments a day in 2024), April 2025: https://blog.youtube/news-and-events/happy-birthday-youtube-20/
- Talk
YouTube — Reimagining video infrastructure to empower YouTube (the VCU, "up to 20-33x improvements in compute efficiency", "more than 500 hours of video content on average is uploaded every minute"), April 2021: https://blog.youtube/inside-youtube/new-era-video-infrastructure/
- Talk
YouTube Official Blog — press page (200 billion+ daily Shorts views; localised in 100+ countries and 80 languages), as of October 2026: https://blog.youtube/press/
- Talk
Cristos Goodrow — On YouTube's recommendation system (signals; valued watchtime; the 2012 watch-time change and 20% drop in views), September 2021: https://blog.youtube/inside-youtube/on-youtubes-recommendation-system/
YouTube help centre and developer docs (as of October 2026):
- Talk
How engagement metrics are counted (views counted from the first frame from 24 August 2026; counts may be slowed, frozen or changed; low-quality playbacks): https://support.google.com/youtube/answer/2991785
- Talk
Low video quality after upload (low quality first; 4K 60-minute example up to 4 hours): https://support.google.com/youtube/answer/71674
- Talk
Upload videos longer than 15 minutes (256 GB or 12 hours): https://support.google.com/youtube/answer/71673
- Talk
YouTube Data API — Resumable uploads (session URI, 256 KB chunks, 308 Resume Incomplete, Range header): https://developers.google.com/youtube/v3/guides/using_resumable_upload_protocol
Papers:
- Talk
Parthasarathy Ranganathan et al. — Warehouse-scale video acceleration: co-design and deployment in the wild, ASPLOS 2021 (task dependency graph, global work queue, closed-GOP chunks in parallel, multiple-output transcoding, popularity buckets, integrity checks): https://research.google/pubs/pub50300/
- Talk
Paul Covington, Jay Adams, Emre Sargin — Deep Neural Networks for YouTube Recommendations, RecSys 2016 (two stages, hundreds of candidates, ANN at serving, expected watch time, example age): https://research.google/pubs/pub45530/
Company filings and press:
- Paper
Alphabet — Q4 and fiscal year 2025 results (YouTube revenue above $60 billion across ads and subscriptions), February 2026: https://s206.q4cdn.com/479360582/files/doc_financials/2025/q4/2025q4-alphabet-earnings-release.pdf
- Talk
Tubefilter — Alphabet reveals YouTube revenue for first time, February 2026: https://tubefilter.com/2026/02/05/alphabet-q4-2025-youtube-earnings-revenue-60-billion/
- Talk
PPC Land — YouTube counts views from the first frame across all formats on August 24 (citing TeamYouTube's announcement of 17 August 2026): https://ppc.land/youtube-counts-views-from-the-first-frame-across-all-formats-on-august-24/
- Talk
Tubefilter — YouTube is making Shorts views go up (Shorts views count every start or replay from 31 March 2025), March 2025: https://www.tubefilter.com/2025/03/26/youtube-shorts-views-counting-stats/
- Talk
BBC News — Gangnam Style music video 'broke' YouTube view limit (YouTube's 32-bit joke; the counter already moved to 64 bits), December 2014: https://www.bbc.com/news/world-asia-30288542
Other engineering references:
- Talk
Vitess — History ("created in 2010 to solve the MySQL scalability challenges that the team at YouTube faced"): https://vitess.io/docs/overview/history/
- Book
Ryan Peterman, Haixia Shi (Meta) — Reducing Instagram's basic video compute time by 94 percent (86.17 CPU-seconds for a 23-second 720p x264 transcode), November 2022: https://engineering.fb.com/2022/11/04/video-engineering/instagram-video-processing-encoding-reduction/
Design note: the architecture, code, thresholds, tiers, rates, sizes and dashboards here are an illustrative design for the problem, not a description of YouTube's internal systems beyond what YouTube and Google have published.
Further Reading#
Related systems to study:
- Read
Netflix (Bonus Problem 6 in this series): the delivery half: adaptive bitrate, per-title encoding and a CDN inside ISPs
- Talk
Instagram (Bonus Problem 21): feed ranking, fan-out and media for photos, videos and stories
- Read
Google Search (Bonus Problem 5): retrieval and ranking over an index of billions
- Read
Twitter / X (Bonus Problem 10): hot keys and fan-out at scale
Foundational concepts (this course):
- Read
Week 2, Day 2 — Idempotency in Practice
- Read
Week 3, Day 3 — Backpressure and Flow Control
- Read
Week 3, Day 4 — Dead Letters and Poison Pills
- Read
Week 1, Day 4 — Hot Keys and Skew
- Read
Week 8, Day 1 — Event Ingestion at Scale
- Read
Week 7, Day 3 — Query Processing and Relevance
"The upload is the only thing you can't rebuild. Everything after it, renditions, counts and recommendations, is a pipeline you can run again."
- 0/7stages cleared
New lessons by email
New lessons, side quests and case files as they ship, at most once a week. No spam, unsubscribe anytime.
Discussion · …
Public: everyone reading this lesson sees comments. Be kind and keep it on the material.