System Design Interviews, Step by Step · Classic Walkthroughs, Part 2

Design a Video Streaming Platform

Upload once, watch anywhere: transcode each video into several qualities split into short segments, serve them from a CDN, and let the player switch quality as the network changes.

Create a free account to save this page, take notes and track your progress. Sign in

The question

"Design a video platform: creators upload videos; viewers watch them on phones, laptops and TVs with smooth playback."

Clarify the requirements

  • Functional: upload videos (up to 4 hours); process them for playback; watch with adaptive quality; view counts; basic search and recommendations (mention only).
  • Scale: 500,000 uploads per day; 100 million daily viewers watching ~30 minutes each.
  • Non-functional: playback starts in under 2 seconds and rarely buffers; uploads survive bad connections; processing can take minutes; storage is the biggest cost.

Estimate the scale

  • Watch traffic: 100M × 30 min ≈ 3 billion minutes/day. At an average 3 Mbps, that's roughly 100+ Tbps at peak: only a CDN can serve this.
  • Storage: 500K uploads × 10 min average × ~5 renditions ≈ 25M minutes of video per day. At ~20 MB per minute across renditions, ≈ 500 TB/day (~180 PB/year): object storage with cheaper tiers for old, rarely watched videos.
  • Upload ingest: 500K/day ≈ 6/s, but each is large; resumable uploads straight to object storage.

API and data model

POST /videos                     -> { video_id, upload_url (resumable, signed) }
PUT  {upload_url}                (chunks; resumes after failures)
GET  /videos/{id}                -> { title, status: processing|ready, manifest_url }
GET  {cdn}/videos/{id}/master.m3u8   (HLS or DASH manifest listing renditions)
POST /videos/{id}/views          (batched by the player)
videos:     video_id | owner | title | status | duration | created_at
renditions: video_id | resolution | bitrate | manifest_key
object storage: originals/{id}, segments/{id}/{rendition}/{n}.ts

High-level design

creator ─► upload service ─► object storage (original) ─► queue ─► transcoding workers (many)
                                                                         │
                                         segments + manifests ◄──────────┘
                                                 │
viewer ─► API (metadata) ─► manifest URL ─► CDN edge ─(miss)─► origin (object storage)
   player picks quality per segment based on measured bandwidth

Deep dives

1. Transcoding and segments. Each upload becomes a ladder of renditions (240p, 480p, 720p, 1080p, 4K) and each rendition is split into segments of 2–6 seconds. Transcoding is split into chunks of the video processed in parallel by many workers (a directed workflow: split → encode chunks → stitch → package → publish), then the video's status flips to ready.

2. Adaptive bitrate streaming. The player downloads a manifest, measures how fast segments arrive, and picks the quality for the next segment: if bandwidth drops, switch down before the buffer runs dry; when it recovers, step back up. HLS and DASH are the standard formats.

An adaptive bitrate player choosing quality per segmentTry it yourself
LADDER = [("240p", 0.4), ("480p", 1.2), ("720p", 3.0), ("1080p", 6.0)]   # (name, Mbps)
SEGMENT = 4         # seconds of video per segment


def choose(bandwidth, buffer):
    # Use at most 80% of measured bandwidth; be cautious when the buffer is low.
    budget = bandwidth * (0.5 if buffer < 8 else 0.8)
    best = LADDER[0]
    for name, mbps in LADDER:
        if mbps <= budget:
            best = (name, mbps)
    return best


network = [8, 8, 7, 2.5, 1.0, 1.0, 3, 6, 9, 9]   # Mbps measured while downloading each segment


def simulate(pick):
    buffer, stalled, chosen = 0.0, 0.0, []
    for bandwidth in network:
        name, mbps = pick(bandwidth, buffer)
        download = SEGMENT * mbps / bandwidth            # seconds to fetch this segment
        if download > buffer:
            stalled += download - buffer                 # the picture freezes while we wait
        buffer = max(0.0, buffer - download) + SEGMENT
        chosen.append(name)
    return chosen, stalled


adaptive, adaptive_stall = simulate(choose)
fixed, fixed_stall = simulate(lambda bandwidth, buffer: LADDER[-1])   # always 1080p
print("network:  ", " ".join(f"{b:>5}" for b in network))
print("adaptive: ", " ".join(f"{q:>5}" for q in adaptive))
print(f"stalled: adaptive {adaptive_stall:.1f}s, always-1080p {fixed_stall:.1f}s")

Output

network:       8     8     7   2.5   1.0   1.0     3     6     9     9
adaptive:   720p  720p  720p  480p  240p  240p  480p  720p 1080p 1080p
stalled: adaptive 1.5s, always-1080p 51.0s

The adaptive player's 1.5 seconds is mostly the first segment loading at startup. The fixed-quality player freezes for most of a minute when the network dips, which is exactly the experience adaptive streaming exists to prevent.

3. The CDN does the heavy lifting. Popular videos are cached at edge locations near viewers; the long tail is fetched from origin on demand. Pre-warm the CDN for expected hits (a big premiere), and use cheaper storage tiers for old videos nobody watches.

4. View counts at scale. Players send view events in batches to a queue; stream processors aggregate counts per minute and update the displayed number periodically. Exact real-time counts aren't needed, and per-view database writes would be enormous.

Trade-offs

  • More renditions improve quality on every connection but multiply storage and transcoding cost; popular videos can get extra renditions later.
  • Segment length: short segments adapt faster and start quicker; long segments compress better and mean fewer requests.
  • Transcode on upload vs on demand: upfront is simpler and faster to play; on-demand saves storage for the many videos that are rarely watched.
  • Own CDN vs commercial CDN: cost at huge scale vs time to market.

What interviewers look for

  • Separating upload/processing (async, queue-driven) from playback (CDN).
  • Renditions + segments + manifests and adaptive bitrate.
  • Realistic bandwidth and storage numbers, and cost awareness.
  • Batched, approximate view counting.

Key points

  • Upload resumably to object storage, then transcode asynchronously in parallel.
  • Each video → several renditions → short segments + a manifest (HLS/DASH).
  • Players adapt quality per segment to measured bandwidth.
  • The CDN serves almost all traffic; counts are aggregated, not written per view.

Practice questions

What is adaptive bitrate streaming?

Answer

The video is encoded at several qualities, each split into short segments listed in a manifest. The player measures download speed and buffer level and chooses the quality for each next segment, dropping quality before the buffer runs out and stepping up when bandwidth allows.

Why is transcoding done asynchronously with a queue and many workers?

Answer

Transcoding a long video into several renditions is CPU-heavy and slow. Queueing the job lets the upload finish immediately, and splitting the video into chunks processed in parallel by many workers makes processing time short and scalable.

Open in the app to mark lessons complete, take notes and track your progress.

Was this page helpful?