Back to Scenarios & Design Questions
Q16Question 16 of 67

How would you design a video streaming platform like YouTube or Netflix

Key Concepts
  • Transcoding is CPU-heavy and must be asynchronous: accept the upload instantly and process via a queue and workers.
  • Adaptive bitrate streaming (HLS/DASH) encodes into multiple renditions; the player picks quality by bandwidth.
  • Serve encoded segments from a CDN edge, not the origin, for global low latency and offloading storage.
  • Object storage holds raw masters and all encoded renditions; the database stores only metadata and pointers.
  • View counts are aggregated asynchronously because per-play DB increments cannot keep up at scale.
  • The system is two halves: a write-heavy async processing pipeline and a read-heavy CDN delivery system.
  • Each pipeline stage is retryable with failures going to a dead-letter queue for inspection.

Definition

Accept uploads fast and transcode asynchronously into multiple renditions, store in object storage, stream adaptively with HLS/DASH, and serve segments from a CDN edge, not the origin.
  • Async processing – Transcoding with FFmpeg is CPU-heavy; accept the upload instantly and process via a queue and background workers.
  • Multiple renditions – Encode at several bitrates (240p, 480p, 720p, 1080p) split into short segments.
  • Adaptive streaming – HLS/DASH manifest lets the player pick the quality that matches current bandwidth, switching segment by segment.
  • CDN edge – Encoded segments are cached worldwide; viewers fetch from the nearest edge.
  • Object storage – Raw and encoded files in S3/GCS; the database stores only metadata.
  • View tracking – Aggregate counts asynchronously; per-play DB increments do not scale.

National Bank of India records training videos for staff and KYC videos for customers like Rajesh. You cannot make them wait while the video is converted into every quality. The upload is accepted instantly, dropped into an in-tray, and workers convert it into small, medium, and large versions. When Rajesh watches on a slow train, the player plays the small version; on office WiFi it plays HD. The video is served from a nearby branch (CDN edge), not head office.

Understanding the Concepts

Why must uploads be processed asynchronously?

Transcoding a video into multiple qualities is CPU-heavy and slow. If done synchronously, the uploader waits minutes and the request times out.

JAVA
package com.bank.video.upload;

public class VideoUploadService {

    private final ObjectStorageClient storage;
    private final MessageQueue queue;
    private final VideoMetadataRepository metadataRepo;

    public VideoUploadService(ObjectStorageClient storage, MessageQueue queue,
                               VideoMetadataRepository metadataRepo) {
        this.storage = storage;
        this.queue = queue;
        this.metadataRepo = metadataRepo;
    }

    public String uploadVideo(String userId, String filename, byte[] rawContent) {
        // Step 1: Store raw master in object storage
        String rawKey = "raw/" + userId + "/" + filename;
        storage.put(rawKey, rawContent);

        // Step 2: Save metadata with status PROCESSING
        String videoId = generateId();
        metadataRepo.save(videoId, userId, rawKey, "PROCESSING");

        // Step 3: Enqueue processing job
        queue.publish("video-processing", new TranscodeJob(videoId, rawKey));

        // Return immediately: uploader does not wait for transcoding
        return videoId;
    }

    private String generateId() {
        return "VID-" + System.currentTimeMillis();
    }
}

What does the processing pipeline look like?

Raw master goes through FFmpeg to produce multiple renditions, each split into segments, plus thumbnails and an HLS manifest.

JAVA
package com.bank.video.worker;

public class TranscodeWorker {

    public void process(TranscodeJob job) {
        // Step 1: Download raw from storage
        byte[] raw = storage.get(job.getRawKey());

        // Step 2: Transcode to multiple renditions
        transcode(raw, "240p", job.getVideoId());
        transcode(raw, "480p", job.getVideoId());
        transcode(raw, "720p", job.getVideoId());
        transcode(raw, "1080p", job.getVideoId());

        // Step 3: Generate thumbnails
        generateThumbnails(raw, job.getVideoId());

        // Step 4: Generate HLS manifest listing all renditions
        generateManifest(job.getVideoId());

        // Step 5: Update status to PUBLISHED
        metadataRepo.updateStatus(job.getVideoId(), "PUBLISHED");
    }

    private void transcode(byte[] raw, String quality, String videoId) {
        // FFmpeg: ffmpeg -i input.mp4 -vf scale=-2:720 -hls_time 4 output_720p.m3u8
        System.out.println("Transcoded " + videoId + " to " + quality);
    }

    private void generateThumbnails(byte[] raw, String videoId) { }
    private void generateManifest(String videoId) { }
}

How does adaptive bitrate streaming work?

The player downloads the manifest, continuously measures bandwidth, and picks the highest quality it can download in time.

JAVA
package com.bank.video.streaming;

public class AdaptiveBitrateExplanation {
    // HLS manifest (master.m3u8) lists renditions:
    // #EXT-X-STREAM-INF:BANDWIDTH=500000,RESOLUTION=426x240
    // 240p/index.m3u8
    // #EXT-X-STREAM-INF:BANDWIDTH=1500000,RESOLUTION=1280x720
    // 720p/index.m3u8
    // #EXT-X-STREAM-INF:BANDWIDTH=4000000,RESOLUTION=1920x1080
    // 1080p/index.m3u8
    //
    // Player behavior:
    // - Measures available bandwidth: 2 Mbps
    // - Picks 720p (needs 1.5 Mbps) because 1080p (4 Mbps) would buffer
    // - If bandwidth drops to 600 Kbps, switches DOWN to 240p
    // - If bandwidth improves, switches UP to 1080p
    // - Switches happen per segment (4 seconds), no rebuffering
}

Why serve from a CDN edge?

Video is huge and heavily watched. Serving every segment from origin saturates it. A CDN caches segments at edge locations worldwide.

JAVA
package com.bank.video.delivery;

public class CdnDelivery {
    // Without CDN:
    //   Viewer in Mumbai -> S3 in US-East -> 200ms latency, origin saturated
    //
    // With CDN:
    //   Viewer in Mumbai -> CDN edge in Mumbai -> 5ms latency, origin offloaded
    //   First viewer triggers cache fill from origin
    //   All subsequent viewers get cached copy from edge
    //   Popular videos: 99%+ served from edge
}

Where do you store raw and encoded files?

Object storage (S3/GCS) is cheap, durable, and infinite. The database holds only metadata.

JAVA
package com.bank.video.model;

public class VideoMetadata {
    private String videoId;
    private String title;
    private String ownerId;
    private String rawStorageKey;      // s3://videos-raw/VID-123/input.mp4
    private String hlsManifestKey;     // s3://videos-encoded/VID-123/master.m3u8
    private String status;             // PROCESSING, PUBLISHED, FAILED
    private long durationSeconds;
    private long viewCount;
    // Database stores pointers, NOT the video bytes
}

How do you track views and analytics?

Never increment a DB row per play. Buffer and aggregate asynchronously.

JAVA
package com.bank.video.analytics;

public class ViewTracker {

    public void trackPlaybackEvent(String videoId, String eventType) {
        // Events: START, PAUSE, SEEK, QUALITY_SWITCH, COMPLETE
        // Emit to Kafka -> analytics pipeline
        // View counts: buffered in Redis, flushed to DB periodically
        // NOT: UPDATE videos SET view_count = view_count + 1 (does not scale)
    }
}

What about retries and failure handling?

Each pipeline stage is retryable. Failed transcodes go to a DLQ for inspection.

JAVA
package com.bank.video.worker;

public class TranscodeRetryHandler {

    public void onFailure(TranscodeJob job, Exception e, int attempt) {
        if (attempt < 3) {
            queue.publishWithDelay("video-processing", job, attempt * 30_000L);
        } else {
            deadLetterQueue.publish(job);
            metadataRepo.updateStatus(job.getVideoId(), "FAILED");
        }
    }
}

What are common interview follow-up questions?

How do you handle live streaming vs on-demand? How do you implement DRM (digital rights management)? How would you add subtitles and multiple audio tracks? How do you monitor quality of experience (rebuffering, startup time)? How do you lifecycle old content to cheaper storage?

Visual Overview

DIAGRAM
┌──────────────────────────────────────────────────────────┐
│          VIDEO STREAMING PLATFORM                         │
├──────────────────────────────────────────────────────────┤
│                                                          │
│  UPLOAD PATH (write-heavy, async):                       │
│                                                          │
│  Client ──upload──▶ API ──raw──▶ S3 (raw master)         │
│                       │                                  │
│                    enqueue                                │
│                       │                                  │
│                       ▼                                  │
│                 ┌────────────┐                            │
│                 │ Queue      │                            │
│                 └─────┬──────┘                            │
│                       │                                  │
│                 ┌─────▼──────────────┐                   │
│                 │ Transcode Workers  │                   │
│                 │ FFmpeg → 240p,480p │                   │
│                 │ 720p,1080p + HLS   │                   │
│                 │ + thumbnails       │                   │
│                 └─────┬──────────────┘                   │
│                       │                                  │
│                 S3 (encoded segments + manifest)          │
│                       │                                  │
│                 status = PUBLISHED                        │
│                                                          │
│  PLAYBACK PATH (read-heavy, CDN):                        │
│                                                          │
│  Viewer ──GET manifest──▶ CDN Edge ──cache miss──▶ S3    │
│  Viewer ──GET segments──▶ CDN Edge (cached, < 5ms)       │
│  Player picks quality by bandwidth (adaptive)            │
│                                                          │
│  ANALYTICS: events → Kafka → pipeline (async)            │
└──────────────────────────────────────────────────────────┘

Key Takeaway

A video streaming platform is two very different systems joined by object storage: a write-heavy async processing pipeline and a read-heavy CDN delivery system. At National Bank of India, Pankaj's early attempt to transcode synchronously caused uploads to time out and the server to melt under concurrent video submissions for KYC verification.

The fix was decoupling: the upload endpoint accepts the raw file (via presigned URL), records metadata with status PROCESSING, and drops a job on a queue. Background workers transcode with FFmpeg into multiple renditions (240p through 1080p), generate thumbnails and an HLS manifest, store everything in S3, and flip the status to PUBLISHED. The viewer never waits on processing.

Playback uses adaptive bitrate streaming: the player downloads the manifest, measures bandwidth, and picks the highest quality it can download per segment, smoothly dropping quality on a weak connection instead of freezing. Segments are served from CDN edges worldwide, hitting the origin only on cache miss. View counts are aggregated asynchronously because per-play DB increments cannot scale.

Remember one sentence: Accept uploads instantly and transcode asynchronously via queue workers into multiple HLS renditions stored in S3, serve segments from a CDN edge for global low latency, and let the player adapt quality to bandwidth segment by segment.

---

Share
Use ← → arrow keys to navigate