~learn.

Side quest 19Video uploads

YouTube Video Platform.

Twenty Million Uploads a Day, Each One Watchable Within Minutes, and a View Count Nobody Can Fake

HARD38 MIN READ6 PHASES · 45 MIN INTERVIEW+120 XP

Mission brief

Design the upload, processing and discovery side of a video platform

Quest log0/7 stages · 0/7 checkpoints · 0/7 quick checks
videos uploaded a day (YouTube, April 2025)
20 million+
of video uploaded every minute (YouTube, 2021)
500+ hours
Shorts views a day (YouTube, June 2025)
200 billion+
more efficient transcoding on YouTube's own chips (2021)
20–33×

Company names and logos are trademarks of their owners, shown only to identify each company. This course isn't affiliated with or endorsed by any of them.

Optional · timed run · 6 phases · 45 min

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.

Intro · 2 min

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.

YouTube, from published numbers
  • 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.

ExplainerThe whole quest: YouTube uploads

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.

Interviewer

"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."

Design the upload, processing and discovery side of a video platform

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
Interviewer

"Leave out video delivery, live streaming, ads and comments. Upload, processing, views, recommendations."

Phase 1 · Stage 1 of 7 · 5 min budget · 2 min read

Phase 1: Requirements Clarification (5 minutes)#

You

"The upload side and the watch side have very different shapes, so I want numbers for both."

Your Questions#

Transcript
You

"How much comes in?"

Interviewer

"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."

You

"How big can one upload be, and what arrives?"

Interviewer

"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."

You

"How quickly must a new video be watchable?"

Interviewer

"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."

You

"What counts as a view?"

Interviewer

"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."

You

"And recommendations?"

Interviewer

"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#

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#

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 · Stage 2 of 7 · 5 min budget · 4 min read

Phase 2: Back of the Envelope Estimation (5 minutes)#

You

"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."

Your turn · checkpoint 1not answered

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?

Think it through, or jot it down. Then compare.

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#

WorksheetUploads (the 3× peak, the 8 Mbps average source bitrate and the 10-minute average session are assumptions)
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#

WorksheetStorage (ratios are assumptions)
  • 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#

WorksheetCompute (Meta's published measurement; the ladder multiplier is an assumption)
  • 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#

WorksheetViews and recommendations (long-form share, event sizes and request rates are assumptions)
  • 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
Quick check3 questions · +5 XP each

Requirements and estimation

  1. 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.

  2. 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.

  3. 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 · Stage 3 of 7 · 10 min budget · 4 min read

Phase 3: High-Level Design (10 minutes)#

Your turn · checkpoint 2not answered

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?

Think it through, or jot it down. Then compare.

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.
You

"Five paths: bytes in, processing, watch, counting, recommending. Only the first two touch the video itself; the other three are about events."

System Architecture#

DiagramVideo platform architecture: upload, processing, views, recommendations
%% 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 queue

KEY 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#

DiagramAsha's upload, from her phone to the first counted 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
Quick check3 questions · +5 XP each

High-level design

  1. 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.

  2. 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.

  3. 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 · Stage 4 of 7 · 20 min budget · 16 min read

Phase 4: Deep Dives (20 minutes)#

Deep Dive 1: Resumable Uploads (Week 2 Concepts: Idempotency, Timeouts)#

You

"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.

Your turn · checkpoint 3not answered

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?

Think it through, or jot it down. Then compare.

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".

DiagramThe life of one upload session
%% 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.
pythonSession · 76 lines
# 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 moov box) 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)#

You

"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.

Your turn · checkpoint 4not answered

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?

Think it through, or jot it down. Then compare.

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:

What goes wrong with one job per video
  • 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:

DiagramOne video's processing task graph
%% 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.
pythonTask · 62 lines
# 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 manifest

Not 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".

Three ways to treat a new upload

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
Quick check4 questions · +5 XP each

Uploads and processing

  1. 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.

  2. 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.

  3. 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.

  4. 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)#

You

"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.

Your turn · checkpoint 5not answered

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?

Think it through, or jot it down. Then compare.

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:

Why 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:

DiagramFrom a play to the public count
%% 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_id would put a viral video on one partition; partitioning by view_id spreads 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.
pythonWindowAggregator · 56 lines
# 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 32

Provisional, verified and engaged#

What a view becomes
  1. Started: the first frame plays and the player sends view_start with a fresh view_id; from 24 August 2026 this is what YouTube counts as a view
  2. Provisional: deduplicated and added within a second or two; the creator sees it in live analytics
  3. Verified: the quality filter clears it (minutes to hours for suspicious traffic); the public count includes it
  4. Engaged: watched beyond the first few seconds; YouTube keeps this as a separate "engaged views" metric for creators
  5. 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)#

You

"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.

Your turn · checkpoint 6not answered

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?

Think it through, or jot it down. Then compare.

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%.

DiagramServing one recommendation request
%% 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
pythonrecommend · 58 lines
# 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)
What to rank by

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.

Quick check4 questions · +5 XP each

Views and recommendations

  1. 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.

  2. 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.

  3. 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.

  4. 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 · Stage 5 of 7 · 5 min budget · 2 min read

Phase 5: Scaling and Edge Cases (5 minutes)#

Interviewer

"Walk me through a video's first day, and then the cases that bite."

A Video's First Day#

Asha's video, 6 pm to 6 pm (YouTube's low-quality-first rule from its help centre; the times are illustrative)
  • 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 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.

Quick check3 questions · +5 XP each

Scaling and edge cases

  1. 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.

  2. 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.

  3. 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 · Stage 6 of 7 · 1 min

Phase 6: Monitoring and Operations#

You

"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#

Video platform dashboard — 21:05 IST (illustrative)
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#

SLOs for upload, processing and views
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
Quick check3 questions · +5 XP each

Reading the dashboard

  1. 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.

  2. 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.

  3. 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 · Stage 7 of 7 · 2 min

Final Challenge: A Three-Hour 4K Upload at Peak Hour#

The interviewer leans back. "One more."

Interviewer

"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."

The concert brief
  • 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
Your turn · checkpoint 7not answered

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.

Think it through, or jot it down. Then compare.

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.
Quick check4 questions · +5 XP each

Final challenge · Quest check

  1. 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.

  2. 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.

  3. 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.

  4. 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.

Wrap-up · 5 min

Interview Conclusion#

Transcript
Interviewer

"Why not just transcode each upload on one big machine?"

You

"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."

Interviewer

"Why not count views with a database increment?"

You

"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."

Interviewer

"The single most important decision?"

You

"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#

Concepts from the 10-week course in a video platform
  • 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#

Why this is a different kind of hard problem
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:

Upload and processing0/3 ready
  • 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:

Views0/2 ready
  • Build a view pipeline that deduplicates, filters and aggregates before counting
  • Shard a hot counter and explain provisional versus verified counts

Recommendations and operations:

Recommendations and operations0/3 ready
  • 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):

YouTube help centre and developer docs (as of October 2026):

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:

Other engineering references:

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."

End of Side quest 19 · YouTube Video Platform
Clear the questFinish YouTube.Stages clear as you read them. Marking complete unlocks after ~6 more min of reading.
  • 0/7stages cleared
design sheet · 6 diagrams · pdfRevise the whole design in ten minutesThe numbers, architecture, sequence, class and state diagrams, decisions and failure modes of this quest on a few printable pages.open the sheet →

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.

Sign in to ask a question or answer one. Reading stays open to everyone.
loading the discussion…