Design Netflix - Video Streaming Platform
Explain Like I'm 5
Imagine you have a magical TV that has every cartoon and movie ever made stored in a giant library far away! When you want to watch your favorite show, you don't have to wait for it to download the whole thing. Instead, the TV sends you tiny pieces of the video one by one, like getting a continuous stream of puzzle pieces that play immediately! The magic library is so smart that it knows what quality your internet can handle - if your internet is slow, it sends smaller pieces so you don't have to wait. If your internet is fast, it sends bigger, clearer pieces! It's like having someone who adjusts the water flow from a hose based on the size of your bucket. The coolest part? The library learns what you like to watch and suggests new shows you might enjoy, just like a friend who knows your favorite games!
Key Features
- •Upload and store millions of movies and TV shows
- •Stream video content with adaptive bitrate (quality adjusts to internet speed)
- •Personalized recommendations based on viewing history
- •Search and browse content by genre, actors, directors
- •Resume watching from where you left off on any device
- •Support for 4K, HDR, and multiple audio tracks/subtitles
- •Download content for offline viewing
Requirements
Functional Requirements:
- •Users can browse and search video catalog
- •Stream video with adaptive bitrate (ABR) based on network conditions
- •Personalized recommendations using ML algorithms
- •Resume playback across devices
- •Support multiple profiles per account
- •Download videos for offline viewing
Non-Functional Requirements:
- •High availability (99.99% uptime)
- •Low latency video startup (<2 seconds)
- •Handle 200 million concurrent streams at peak
- •Scalable to petabytes of video content
- •Global CDN for fast content delivery
Capacity Estimation
Assumptions:
- •300 million total subscribers worldwide
- •200 million daily active users
- •Each user watches 2 hours of content per day on average
- •Average video bitrate: 5 Mbps (HD quality)
- •Total content library: 100,000 hours of video
Storage Calculation:
- •Per video hour: ~2.25GB (5 Mbps × 3600s ÷ 8)
- •Total raw content: 100,000 hours × 2.25GB = 225TB
- •Multiple qualities (4K, HD, SD): 225TB × 5 = 1.125PB
- •Replicas + backups: 1.125PB × 3 = ~3.4PB
Bandwidth Calculation:
- •Peak concurrent users: 200M × 0.3 (30% watching) = 60M
- •Bandwidth per stream: 5 Mbps
- •Total bandwidth: 60M × 5 Mbps = 300 Petabits/second = 37.5 Petabytes/s
- •With CDN edge caching, reduce by 95%: ~1.875 PB/s from origin
API Design
1. Search Content:
GET /api/v1/content/search?query=stranger+things&limit=20
Response:
{
"results": [
{
"content_id": "tt4574334",
"title": "Stranger Things",
"type": "series",
"thumbnail_url": "https://cdn.netflix.com/...",
"year": 2016,
"rating": "TV-14",
"match_score": 0.98
}
],
"total": 1
}2. Get Content Details:
GET /api/v1/content/{content_id}
Response:
{
"content_id": "tt4574334",
"title": "Stranger Things",
"description": "When a young boy disappears...",
"genres": ["Drama", "Fantasy", "Horror"],
"seasons": 4,
"episodes": 34,
"cast": ["Millie Bobby Brown", "Finn Wolfhard"],
"trailer_url": "https://cdn.netflix.com/trailers/...",
"available_qualities": ["4K", "1080p", "720p", "480p"]
}3. Get Video Stream (Adaptive Bitrate):
GET /api/v1/stream/{content_id}/{episode_id}
Response:
{
"stream_url": "https://cdn.netflix.com/manifest.mpd",
"format": "DASH",
"available_bitrates": [
{"quality": "4K", "bitrate": 25000, "resolution": "3840x2160"},
{"quality": "1080p", "bitrate": 5000, "resolution": "1920x1080"},
{"quality": "720p", "bitrate": 3000, "resolution": "1280x720"},
{"quality": "480p", "bitrate": 1500, "resolution": "854x480"}
],
"subtitles": ["en", "es", "fr", "de"],
"audio_tracks": ["en-5.1", "en-stereo", "es-stereo"]
}4. Get Recommendations:
GET /api/v1/recommendations/user/{user_id}?limit=20
Response:
{
"recommendations": [
{
"content_id": "tt0944947",
"title": "Game of Thrones",
"thumbnail_url": "https://cdn.netflix.com/...",
"prediction_score": 0.92,
"reason": "Because you watched Stranger Things"
}
]
}Database Design
Content Catalog (PostgreSQL):
CREATE TABLE content (
content_id VARCHAR(50) PRIMARY KEY,
title VARCHAR(255),
description TEXT,
type ENUM('movie', 'series'),
release_year INT,
rating VARCHAR(10),
duration_minutes INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_title (title),
INDEX idx_type (type),
INDEX idx_year (release_year)
);Video Assets (Metadata DB):
CREATE TABLE video_assets ( asset_id VARCHAR(50) PRIMARY KEY, content_id VARCHAR(50), quality VARCHAR(10), -- '4K', '1080p', '720p', '480p' bitrate INT, codec VARCHAR(20), file_size_gb DECIMAL(10,2), cdn_url TEXT, FOREIGN KEY (content_id) REFERENCES content(content_id), INDEX idx_content (content_id), INDEX idx_quality (quality) );
Viewing History (Cassandra - High Write Volume):
CREATE TABLE viewing_history ( user_id BIGINT, content_id VARCHAR(50), episode_id VARCHAR(50), watch_timestamp TIMESTAMP, progress_seconds INT, completed BOOLEAN, device_type VARCHAR(50), PRIMARY KEY ((user_id), watch_timestamp) ) WITH CLUSTERING ORDER BY (watch_timestamp DESC);
User Profiles (PostgreSQL):
CREATE TABLE user_profiles ( profile_id BIGINT PRIMARY KEY, account_id BIGINT, profile_name VARCHAR(100), avatar_url VARCHAR(255), is_kids_profile BOOLEAN DEFAULT FALSE, language VARCHAR(10), preferences JSONB, -- viewing preferences, maturity level created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );
High-Level Architecture
┌──────────────┐
│ Clients │
│ (Web, Mobile,│
│ Smart TV) │
└──────┬───────┘
│
│ HTTPS
│
┌──────▼────────────────────────────────────────┐
│ CDN (CloudFront / Akamai) │
│ Edge Servers in 200+ Locations │
│ • Caches video segments │
│ • Reduces latency (serve from nearest edge) │
└──────┬────────────────────────────────────────┘
│
│ Cache Miss
│
┌──────▼──────┐ ┌─────────────┐ ┌──────────────┐
│ API │ │ Search │ │ Encoding │
│ Gateway │──────│ Service │ │ Service │
│ (Kong) │ │(Elasticsearch) │ (Video │
└──────┬──────┘ └─────────────┘ │ Transcoding) │
│ └──────────────┘
│
├───────────┬──────────────┬──────────────┐
│ │ │ │
┌──────▼──────┐ ┌──▼────────┐ ┌──▼──────────┐ ┌▼──────────────┐
│ Content │ │ Viewing │ │Recommendation│ │ User │
│ Service │ │ Service │ │ Service │ │ Service │
└──────┬──────┘ └──┬────────┘ └──┬───────────┘ └┬──────────────┘
│ │ │ │
│ │ │ │
┌──────▼──────┐ ┌──▼─────────┐ ┌▼──────────┐ ┌─▼──────────┐
│ PostgreSQL │ │ Cassandra │ │ Spark │ │ PostgreSQL │
│ (Content) │ │ (History) │ │ (ML) │ │ (Users) │
└─────────────┘ └────────────┘ └───────────┘ └────────────┘
│
┌──────▼──────────┐
│ S3 / Blob │
│ Video Storage │
│ (Raw files) │
└─────────────────┘Deep Dive: Key Components
1. Video Encoding Pipeline
When a new video is uploaded, it must be transcoded into multiple qualities and formats for adaptive bitrate streaming.
public class VideoEncodingService { private S3Client s3Client; private KafkaProducer kafkaProducer; private TranscodingQueue transcodingQueue; /** * Initiates the video encoding pipeline. * Transcodes video into multiple qualities for adaptive streaming. */ public void encodeVideo(String contentId, String originalVideoUrl) { System.out.println("Starting encoding for content: " + contentId); // 1. Download original video from S3 byte[] originalVideo = s3Client.download(originalVideoUrl); // 2. Define target qualities and bitrates EncodingProfile[] profiles = { new EncodingProfile("4K", 3840, 2160, 25000, "h265"), new EncodingProfile("1080p", 1920, 1080, 5000, "h264"), new EncodingProfile("720p", 1280, 720, 3000, "h264"), new EncodingProfile("480p", 854, 480, 1500, "h264"), new EncodingProfile("360p", 640, 360, 800, "h264") }; // 3. Submit encoding jobs for each quality (parallel processing) List<CompletableFuture<EncodingResult>> encodingJobs = new ArrayList<>(); for (EncodingProfile profile : profiles) { CompletableFuture<EncodingResult> job = CompletableFuture.supplyAsync(() -> { return transcodeVideo(originalVideo, profile, contentId); }); encodingJobs.add(job); } // 4. Wait for all encoding jobs to complete CompletableFuture.allOf(encodingJobs.toArray(new CompletableFuture[0])) .thenRun(() -> { System.out.println("All encodings completed for: " + contentId); // 5. Generate DASH manifest for adaptive streaming generateDashManifest(contentId); // 6. Publish completion event kafkaProducer.send("video-encoding-completed", new VideoReadyEvent(contentId, profiles.length)); }); } /** * Transcodes video to specific quality using FFmpeg. */ private EncodingResult transcodeVideo(byte[] video, EncodingProfile profile, String contentId) { try { System.out.println("Encoding " + profile.quality + " for " + contentId); // Simulate transcoding (in reality, calls FFmpeg or cloud encoding service) // ffmpeg -i input.mp4 -s 1920x1080 -b:v 5000k -c:v h264 output.mp4 String outputPath = "videos/" + contentId + "/" + profile.quality + ".mp4"; // Upload encoded video to S3 String s3Url = s3Client.upload(outputPath, video); // Save metadata to database VideoAsset asset = new VideoAsset( contentId, profile.quality, profile.bitrate, profile.codec, s3Url ); saveVideoAsset(asset); return new EncodingResult(profile.quality, s3Url, true); } catch (Exception e) { System.err.println("Encoding failed for " + profile.quality); return new EncodingResult(profile.quality, null, false); } } /** * Generates DASH (Dynamic Adaptive Streaming over HTTP) manifest. * This manifest tells the video player which qualities are available. */ private void generateDashManifest(String contentId) { // Fetch all encoded video assets List<VideoAsset> assets = getVideoAssets(contentId); StringBuilder manifest = new StringBuilder(); manifest.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); manifest.append("<MPD xmlns=\"urn:mpeg:dash:schema:mpd:2011\">\n"); for (VideoAsset asset : assets) { manifest.append(" <Representation id=\"" + asset.quality + "\"\n"); manifest.append(" bandwidth=\"" + asset.bitrate + "\"\n"); manifest.append(" codecs=\"" + asset.codec + "\">\n"); manifest.append(" <BaseURL>" + asset.cdnUrl + "</BaseURL>\n"); manifest.append(" </Representation>\n"); } manifest.append("</MPD>"); // Upload manifest to S3 String manifestUrl = "videos/" + contentId + "/manifest.mpd"; s3Client.upload(manifestUrl, manifest.toString().getBytes()); } static class EncodingProfile { String quality; int width, height, bitrate; String codec; EncodingProfile(String quality, int width, int height, int bitrate, String codec) { this.quality = quality; this.width = width; this.height = height; this.bitrate = bitrate; this.codec = codec; } }}2. Adaptive Bitrate Streaming (ABR)
The video player continuously monitors network speed and adjusts video quality in real-time to prevent buffering.
public class AdaptiveBitratePlayer { private String manifestUrl; private List<VideoQuality> availableQualities; private VideoQuality currentQuality; private NetworkMonitor networkMonitor; private int bufferSeconds = 30; // Target buffer size /** * Adaptive streaming player that adjusts quality based on network speed. */ public AdaptiveBitratePlayer(String manifestUrl) { this.manifestUrl = manifestUrl; this.availableQualities = parseManifest(manifestUrl); this.networkMonitor = new NetworkMonitor(); // Start with medium quality this.currentQuality = availableQualities.get(2); // 720p } /** * Main playback loop - continuously monitors and adjusts quality. */ public void play() { int segmentNumber = 0; while (true) { // 1. Measure current network bandwidth double bandwidthMbps = networkMonitor.measureBandwidth(); // 2. Check current buffer level int currentBuffer = getBufferLevel(); // 3. Decide if we need to switch quality VideoQuality newQuality = selectOptimalQuality( bandwidthMbps, currentBuffer ); if (!newQuality.equals(currentQuality)) { System.out.println("Switching quality: " + currentQuality.resolution + " -> " + newQuality.resolution); currentQuality = newQuality; } // 4. Fetch next video segment at current quality String segmentUrl = currentQuality.baseUrl + "/segment_" + segmentNumber + ".m4s"; byte[] segment = downloadSegment(segmentUrl); // 5. Add to playback buffer addToBuffer(segment); segmentNumber++; // Wait for next segment (typically 2-10 seconds) sleep(2000); } } /** * Selects optimal quality based on bandwidth and buffer health. */ private VideoQuality selectOptimalQuality(double bandwidthMbps, int currentBuffer) { // Safety margin: use 80% of measured bandwidth double usableBandwidth = bandwidthMbps * 0.8; // Buffer-based adjustment if (currentBuffer < 10) { // Buffer running low - prioritize stability over quality usableBandwidth *= 0.6; // Be conservative } else if (currentBuffer > 40) { // Buffer is healthy - can try higher quality usableBandwidth *= 1.2; } // Find highest quality that fits within bandwidth VideoQuality selected = availableQualities.get(0); // Start with lowest for (VideoQuality quality : availableQualities) { if (quality.bitrateMbps <= usableBandwidth) { selected = quality; } else { break; // Qualities are sorted by bitrate } } return selected; } /** * Parses DASH manifest to extract available qualities. */ private List<VideoQuality> parseManifest(String manifestUrl) { // Parse MPD file to extract representations // Simplified for demonstration return Arrays.asList( new VideoQuality("360p", 0.8, "https://cdn.netflix.com/360p"), new VideoQuality("480p", 1.5, "https://cdn.netflix.com/480p"), new VideoQuality("720p", 3.0, "https://cdn.netflix.com/720p"), new VideoQuality("1080p", 5.0, "https://cdn.netflix.com/1080p"), new VideoQuality("4K", 25.0, "https://cdn.netflix.com/4k") ); } static class VideoQuality { String resolution; double bitrateMbps; String baseUrl; VideoQuality(String resolution, double bitrateMbps, String baseUrl) { this.resolution = resolution; this.bitrateMbps = bitrateMbps; this.baseUrl = baseUrl; } @Override public boolean equals(Object obj) { if (obj instanceof VideoQuality) { return this.resolution.equals(((VideoQuality) obj).resolution); } return false; } } static class NetworkMonitor { /** * Measures current network bandwidth by tracking download speeds. */ public double measureBandwidth() { // In reality: measure actual download speed of recent segments // For demo, simulate varying bandwidth return 3.0 + Math.random() * 5.0; // 3-8 Mbps } }}3. Recommendation Engine
Machine learning model that predicts what content users will enjoy based on their viewing history and similar users' behavior (collaborative filtering).
public class RecommendationService { private CassandraClient cassandra; private RedisCache cache; private MLModel collaborativeFilteringModel; /** * Generates personalized recommendations for a user. * Uses collaborative filtering and content-based filtering. */ public List<Recommendation> getRecommendations(long userId, int limit) { // 1. Check cache first String cacheKey = "recommendations:" + userId; List<Recommendation> cached = cache.get(cacheKey); if (cached != null) { return cached.subList(0, Math.min(limit, cached.size())); } // 2. Fetch user's viewing history List<ViewingHistory> history = getViewingHistory(userId); // 3. Collaborative filtering: find similar users List<Long> similarUsers = findSimilarUsers(userId, history); // 4. Get what similar users watched and liked Map<String, Double> candidateScores = new HashMap<>(); for (Long similarUserId : similarUsers) { List<ViewingHistory> theirHistory = getViewingHistory(similarUserId); for (ViewingHistory view : theirHistory) { // Skip if current user already watched if (userHasWatched(userId, view.contentId)) { continue; } // Calculate recommendation score double score = calculateScore(userId, view.contentId, similarUserId, view); candidateScores.merge(view.contentId, score, Double::sum); } } // 5. Content-based filtering: find similar content for (ViewingHistory view : history) { if (view.completed) { List<String> similarContent = findSimilarContent(view.contentId); for (String contentId : similarContent) { if (!userHasWatched(userId, contentId)) { double score = 0.5; // Lower weight than collaborative candidateScores.merge(contentId, score, Double::sum); } } } } // 6. Sort by score and return top N List<Recommendation> recommendations = candidateScores.entrySet() .stream() .sorted(Map.Entry.<String, Double>comparingByValue().reversed()) .limit(limit) .map(e -> new Recommendation(e.getKey(), e.getValue())) .collect(Collectors.toList()); // 7. Cache results for 1 hour cache.setWithExpiry(cacheKey, recommendations, 3600); return recommendations; } /** * Finds users with similar viewing patterns using cosine similarity. */ private List<Long> findSimilarUsers(long userId, List<ViewingHistory> userHistory) { // In production: use Spark to compute user similarity matrix // For demo, simplified approach // Get content IDs user has watched Set<String> userContentIds = userHistory.stream() .map(h -> h.contentId) .collect(Collectors.toSet()); // Query database for users who watched similar content List<Long> similarUsers = cassandra.query( "SELECT DISTINCT user_id FROM viewing_history " + "WHERE content_id IN ? LIMIT 100", userContentIds.toArray() ); return similarUsers.stream() .filter(uid -> uid != userId) .limit(20) .collect(Collectors.toList()); } /** * Calculates recommendation score for a content item. */ private double calculateScore(long userId, String contentId, long similarUserId, ViewingHistory view) { double score = 1.0; // Factor 1: User similarity (computed offline) double userSimilarity = getUserSimilarity(userId, similarUserId); score *= userSimilarity; // Factor 2: Content popularity int viewCount = getContentViewCount(contentId); double popularityScore = Math.log(viewCount + 1) / 10.0; score *= (1.0 + popularityScore); // Factor 3: Recency (recent content weighted higher) long daysSinceRelease = getDaysSinceRelease(contentId); if (daysSinceRelease < 30) { score *= 1.5; // Boost new content } // Factor 4: Completion rate (if similar user finished it) if (view.completed) { score *= 1.3; } return score; } /** * Finds content similar to given content based on metadata. */ private List<String> findSimilarContent(String contentId) { // Get content metadata Content content = getContent(contentId); // Find content with overlapping genres, cast, director List<String> similar = cassandra.query( "SELECT content_id FROM content " + "WHERE genres CONTAINS ANY ? " + "AND content_id != ? " + "LIMIT 10", content.genres, contentId ); return similar; } private boolean userHasWatched(long userId, String contentId) { return cassandra.exists( "SELECT * FROM viewing_history " + "WHERE user_id = ? AND content_id = ?", userId, contentId ); } static class Recommendation { String contentId; double score; Recommendation(String contentId, double score) { this.contentId = contentId; this.score = score; } }}Trade-offs and Optimizations
1. CDN vs Origin Servers
CDN: 95% cache hit rate, low latency, expensive. Origin: Centralized, cheaper, higher latency. Solution: Multi-tier CDN with edge caching.
2. DASH vs HLS Streaming
DASH: Open standard, flexible. HLS: Apple devices, simpler. Netflix uses both depending on device.
3. SQL vs NoSQL for Viewing History
SQL: ACID guarantees, complex queries. Cassandra: High write throughput (millions of views/sec), eventual consistency. Use Cassandra for viewing history.
4. Real-time vs Batch Recommendations
Real-time: Instant updates, expensive compute. Batch: Update every few hours, cheaper. Hybrid: Cache pre-computed recommendations, refresh periodically.
Optimizations:
- ✓Use CDN edge servers in 200+ locations for <50ms video startup
- ✓Pre-encode videos in 5+ qualities for all devices
- ✓Implement Open Connect (Netflix's custom CDN) in ISP networks
- ✓Cache popular content at edge, fetch long-tail from origin
- ✓Use Kafka for real-time event streaming (video views, clicks)
- ✓Implement smart caching: predict what user will watch next
Follow-up Interview Questions
Q: How do you handle video piracy and DRM?
A: Use DRM (Digital Rights Management) like Widevine, FairPlay. Encrypt video segments with unique keys per user session. Implement watermarking to trace leaked content. Monitor for suspicious download patterns.
Q: How would you reduce CDN costs?
A: Implement tiered storage (hot/warm/cold). Use cheaper CDN providers for less popular content. Deploy Open Connect appliances in ISP data centers. Optimize encoding (better codecs like AV1). Cache popular content more aggressively.
Q: How do you ensure smooth playback during network fluctuations?
A: Maintain 30-second buffer. Implement adaptive bitrate with quick quality switching. Pre-buffer higher quality when bandwidth is good. Use DASH/HLS for seamless quality transitions without interruption.
Q: How would you implement 'download for offline viewing'?
A: Store encrypted video segments locally on device. Implement expiration (30 days). Limit concurrent downloads per account. Use device storage management. Require periodic license refresh (every 48 hours) to verify subscription.
Q: How do you handle live streaming events?
A: Use low-latency HLS or CMAF for <5 second delay. Implement chunked encoding (encode as video is captured). Use edge servers for real-time distribution. Scale CDN capacity before event. Have fallback servers for redundancy.
Real-World Implementation
Netflix's actual tech stack:
- ✓AWS for compute and storage (200+ microservices)
- ✓Open Connect CDN with 17,000+ servers in ISP networks
- ✓Apache Cassandra for viewing history (hundreds of nodes)
- ✓Apache Kafka for real-time event streaming
- ✓Apache Spark for ML recommendation models
- ✓EVCache (Memcached wrapper) for distributed caching
- ✓Zuul API Gateway for routing and filtering
- ✓Hystrix for circuit breaker pattern and fault tolerance