System Design #15: Design YouTube

SYSTEM DESIGN #15 · INTERVIEW GUIDE

Design YouTube

YouTube serves over 500 hours of video uploaded every minute and 1 billion hours watched every day — on every screen, every connection speed, every country. The engineering behind that experience is a masterclass in distributed systems: a multi-stage transcoding pipeline that produces 12 quality variants per video, HLS chunked delivery over 200+ CDN PoPs, adaptive bitrate switching that adjusts quality mid-stream based on bandwidth, and a Two-Tower recommendation model that serves personalised next-watch predictions in under 100ms. This guide covers upload processing, DAG-based transcoding orchestration, CDN cache warming, comment fanout, and the ML ranking pipeline. You will leave knowing exactly how to answer ‘Design YouTube’ in a senior staff engineering interview from first principles.

HLS / DASHFFmpegTwo-Tower ModelScaNNCDN

💡

The Gist — What Problem Are We Solving?

A global video library where anyone can publish and anyone can watch

YouTube receives 500 hours of video every minute. Each upload must be converted into dozens of formats and resolutions for every device and connection speed. The delivery network streams the right quality chunk adaptively based on your bandwidth. And from 800 million videos, the recommendation system must predict the one you want to watch next — in under 200ms.

💬

Think of it as a global TV network with 800 million channels, where every viewer gets a personalised schedule and every video automatically works on every device at every connection speed.

Functional Requirements

These are the capabilities the system must deliver — what users and operators can actually do with it.

📤
Video Upload

📤Accept raw video; validate; store; trigger transcoding pipeline

🎬
Transcoding

🎬Convert to multiple codecs (H.264, H.265, AV1) and resolutions (144p–4K)

📺
Video Playback

📺Adaptive bitrate streaming; resume from last position; offline download

🔍
Search & Discovery

🔍Full-text search on title/description; browse by category

👍
Engagement

👍Views, likes, comments, shares; subscriptions; notifications

Non-Functional Requirements

These define how well the system must perform — the quality attributes that separate a toy from a production system.

📤 Upload Throughput

📤500 hours of video uploaded per minute

⏱️ Playback Start

⏱️<2s time-to-first-frame on good connection

📡 ABR

📡Seamless quality switching; maintain <500ms rebuffer rate

🔍 Search Latency

🔍<200ms search results

🤖 Recommendation

🤖<200ms personalised feed generation

📊

Key Metrics — The Numbers That Define This System

The headline numbers to know cold — and be ready to explain how each one is achieved.

500hrs
uploaded/min
800M
total videos
2-10s
HLS segment
12+
renditions/video
10× raw
storage multiplier
🏗️

System Architecture Diagram

Full data flow from source to serving. Each layer scales independently.

Ingestion
YouTube Architecture
Upload

Object Storage
raw

Transcoding DAG
GPU workers, parallel

CDN
HLS segments

Processing
Client Player
ABR); Metadata

Postgres + Elasticsearch; Engagement

Kafka

Flink

Storage
Cassandra; Recommendations

Two-tower model

Faiss ANN

Feed API

🗺️

End-to-End User Journey

Trace a single request end-to-end — the story interviewers want you to tell fluently.

1
Creator uploads video

— Resumable upload to Object Storage (GCS). Metadata (title, description, tags) saved to Postgres. Upload complete event published to Kafka.

2
Transcoding pipeline starts

— DAG orchestrator (Airflow) creates parallel tasks per scene per resolution. GPU workers encode 12+ renditions. Segments (2-10s HLS chunks) stored in GCS.

3
Video goes live

— All required renditions complete → status=PUBLISHED. Manifest file (.m3u8) generated listing all quality levels. CDN pre-warms for popular creators.

4
Viewer opens video

— Client fetches manifest from CDN. Downloads first segment at adaptive quality. Maintains 30-second buffer.

5
ABR in action

— After each segment download, player measures throughput. Fast connection → request higher quality segment. Slow → request lower. Per-segment decision.

🔭

High-Level Design — Component Breakdown

Core components — each with a single, well-defined responsibility. The key architectural insight: each layer scales independently, and failure in one component is isolated from the rest.

1
Upload API
2
Transcoding
3
GCS/CDN
4
Player
5
Kafka+Flink
6
Two-tower
1 — Upload API

Handles responsibilities for the Upload API layer. Designed for independent horizontal scaling — additional instances added without architectural changes. Communicates asynchronously with adjacent components to maximise throughput and fault isolation.

2 — Transcoding

Handles responsibilities for the Transcoding layer. Designed for independent horizontal scaling — additional instances added without architectural changes. Communicates asynchronously with adjacent components to maximise throughput and fault isolation.

3 — GCS/CDN

Content Delivery Network serving static assets and media from 200+ PoPs. Cache-Control: immutable for media segments. Origin shield reduces origin load by 99%. Multi-CDN failover for resilience.

4 — Player

Handles responsibilities for the Player layer. Designed for independent horizontal scaling — additional instances added without architectural changes. Communicates asynchronously with adjacent components to maximise throughput and fault isolation.

5 — Kafka+Flink

Distributed event bus with RF=3 for durability. Partitioned by user_id_hash for per-user ordering. LZ4 compression reduces storage cost by 60%. Exactly-once semantics via idempotent producers and transactional consumers.

6 — Two-tower

Handles responsibilities for the Two-tower layer. Designed for independent horizontal scaling — additional instances added without architectural changes. Communicates asynchronously with adjacent components to maximise throughput and fault isolation.

🔬

Low-Level Design — Deep Dives

Deep dives worth explaining in detail in any senior engineering interview. For each: know the data structure, the algorithm, the why, and the trade-off you made.

1 — Multi-Stage Transcoding DAG
GPU Workers · 12 Variants

Upload triggers a Temporal DAG with parallel tasks: one task per output variant (240p, 360p, 480p, 720p, 1080p, 1440p, 4K) × 2 codecs (H.264, AV1). GPU workers pull tasks from SQS. Each worker runs FFmpeg: ffmpeg -i input.mp4 -vf scale=1280:720 -c:v libx264 -crf 23 -preset fast output_720p.mp4. Progress tracked per 2-second HLS segment — failed tasks resume from last complete segment. Master playlist (.m3u8) generated after all variants complete.

# Temporal workflow: parallel transcoding
async with workflow.unsafe.imports_passed_through():
tasks = [
workflow.execute_activity(transcode, variant=v)
for v in VARIANTS
]
await asyncio.gather(*tasks)
await workflow.execute_activity(generate_manifest)

2 — HLS Segment Storage & CDN
2-second Segments · 200+ PoPs

HLS segments (2-second .ts files) stored in S3 with CDN prefix. CDN Cache-Control: max-age=31536000, immutable — segments never change once written. Master playlist (.m3u8) has Cache-Control: max-age=5 — allows late-segment updates. CDN pre-warms trending videos by proactively pushing segments to edge caches before viral traffic hits. Origin shield reduces S3 requests by 99.9% for popular content.

s3.put_object(
Bucket=’video-segments’,
Key=f’videos/{video_id}/{variant}/seg_{seq}.ts’,
Body=segment_bytes,
CacheControl=’public, max-age=31536000, immutable’,
ContentType=’video/mp2t’
)

3 — Two-Tower Recommendation Model
User + Item Embeddings · ANN

Two-Tower architecture: user tower (watch history, age, region → 128-dim embedding); item tower (title, tags, category, views, CTR → 128-dim embedding). Similarity = dot product of normalised embeddings. Trained on watch events with negative sampling (4:1 negative ratio). Deployed as ScaNN (Scalable Nearest Neighbours) index over all 800M item embeddings. Query: retrieve top-1000 candidates in <50ms. Lightweight ranker re-ranks on engagement signals (watch time, likes, shares).

class TwoTowerModel(nn.Module):
def forward(self, user_feats, item_feats):
u_emb = self.user_tower(user_feats) # [B, 128]
i_emb = self.item_tower(item_feats) # [N, 128]
scores = torch.matmul(u_emb, i_emb.T) # [B, N]
return F.normalize(scores, dim=-1)

4 — ABR Player Logic
BOLA · Buffer-based Selection

BOLA (Buffer Occupancy based Lyapunov Algorithm) controls bitrate selection. Utility of quality level q: V×log(bitrate_q) – buffer_penalty. Player selects quality maximising utility given current buffer level. Startup: always 360p for fast first-frame. Buffer target: 30 seconds. Bitrate switch: only at segment boundaries (every 2 seconds). Network bandwidth estimated as 1.2× current download rate (headroom for bitrate switch). Stall avoidance: drop to lowest quality if buffer <5 seconds.

def select_quality(buffer_level, bandwidth, variants):
if buffer_level < 5: # stall prevention return variants[0] # lowest quality scores = [V * log(v.bitrate) - (BUFFER_TARGET - buffer_level) * v.bitrate / bandwidth for v in variants] return variants[scores.index(max(scores))]

⚖️

Trade-offs & Decision Log

Every senior interview comes down to these decisions. Know the exact trade-off, the reasoning, and the specific numbers that justify each choice.

⚖️ Monolithic Transcoder vs Distributed DAG Pipeline


Distributed DAG (Temporal/Airflow) ✅ Chosen
  • Each quality variant transcoded in parallel — 12× faster than sequential
  • Failed tasks retried independently — no full reprocessing
  • GPU workers autoscale based on upload queue depth
  • Higher orchestration complexity vs single FFmpeg process

Single FFmpeg process per video
  • Simple — one command produces all variants
  • Sequential processing — 4K video takes 3h instead of 15min
  • Single GPU failure retranscodes everything
  • No partial progress — must restart from scratch on failure

💡

Decision: Distributed DAG with one task per quality variant; checkpointed segment-level progress for resilience to GPU preemption

⚖️ Push vs Pull Feed for Recommendations


Two-Tower Model (pull at serve time) ✅ Chosen
  • Personalised to watch history at the moment of request
  • Handles cold-start with item embeddings only
  • Scales to 2.5B users without pre-computing all pairs
  • 100ms inference latency requires ANN index (ScaNN)

Pre-computed recommendations (push)
  • Zero inference latency — recommendations already in cache
  • Stale by hours — doesn’t react to watch history from last hour
  • Storage cost: 2.5B users × 50 recommendations × 8 bytes = 1TB
  • Infeasible for item catalogue of 800M videos

💡

Decision: Two-Tower model with ANN retrieval (ScaNN) for top-1000 candidates; lightweight ranker re-ranks on watch-time signals

🎯

Interview Questions — Answered
The exact questions interviewers ask — with production-grade answers

Q1
How does adaptive bitrate (ABR) work during playback?

HLS manifest lists 12 quality variants from 240p/300Kbps to 4K/15Mbps. The player (Video.js/Shaka) measures download bandwidth every 2 seconds using the manifest fetch and segment download times. ABR algorithm (BOLA — Buffer-Occupancy based Lyapunov Algorithm): selects the highest bitrate variant whose download time ≤ target buffer occupancy threshold. If buffer drops below 15 seconds, switches to lower quality to prevent stall. If buffer exceeds 60 seconds, switches up. Switches are seamless — the player switches at the next segment boundary (2-second segments). Startup quality is always 360p regardless of bandwidth to ensure fast first-frame time.

Q2
How is the video upload pipeline made resilient to failures mid-upload?

Resumable uploads via GCS/S3 multipart upload: (1) Client requests upload session URL. (2) Video is chunked into 10MB parts. (3) Each part uploaded independently — network failure retries only the failed part. (4) S3 CompleteMultipartUpload called after all parts confirmed. Idempotency: each chunk has a content hash; duplicate uploads are deduplicated at S3 level. On server-side failure mid-transcoding: DAG task is checkpointed at the segment level — restart resumes from last successful segment, not from start. Transcode status stored in DynamoDB: {video_id, task_id, last_completed_segment, status}. Dead letter queue for videos that fail after 3 retries — manually reviewed by content team.

Q3
How does the recommendation system avoid filter bubbles?

Three mechanisms to prevent over-personalisation: (1) Exploration budget — 10% of recommendation slots are allocated to ‘exploration’: random sampling from outside the user’s typical content clusters using Thompson Sampling. (2) Freshness bonus — videos published in the last 7 days receive a freshness multiplier in the ranking model to promote new content discovery. (3) Diversity constraint — post-ranking filter ensures no more than 2 consecutive recommendations from the same channel, and topic diversity is maximised using Maximum Marginal Relevance (MMR). A/B tests show these constraints reduce watch time by <2% while significantly improving long-term retention by preventing recommendation fatigue.

System Design Series · Every Tuesday & Thursday

Level up your system design interviews

Each post covers Gist, Functional & Non-Functional Requirements, Key Metrics, System Diagram, User Journey, HLD, LLD, and Trade-offs & FAQs.

Subscribe to never miss a post →


Categories: System Design

Tags: , , , , , ,

Leave a Reply

Discover more from Cloud Wizard Inc.

Subscribe now to keep reading and get access to the full archive.

Continue reading