1. Requirements
Functional Requirements
| Requirement |
Details |
Priority |
| Stream Music |
Play tracks with low latency, adaptive quality, minimal buffering |
P0 |
| Playlists |
Create, edit, share user/collaborative playlists |
P0 |
| Search |
Find tracks, albums, artists, playlists with autocomplete |
P0 |
| Personalization |
Discover Weekly, Daily Mix, Release Radar, recommendations |
P0 |
| Offline Download |
Download tracks/playlists for offline playback with DRM |
P1 |
| Social Features |
Follow users, share tracks, collaborative playlists |
P1 |
Non-Functional Requirements
| Category |
Requirement |
Target |
| Scale |
Monthly Active Users |
600M MAU |
| Scale |
Music Catalog |
100M tracks, 8M artists, 4M albums |
| Latency |
Playback Start Time |
<200ms to first byte |
| Latency |
Search Response |
<100ms p99 |
| Availability |
Streaming Service |
99.9% uptime |
| Throughput |
Peak Concurrent Streams |
50M concurrent users |
| Storage |
Audio Files |
~100PB (multiple qualities per track) |
| Bandwidth |
Peak Traffic |
~15 Tbps (320kbps avg, 50M concurrent) |
Back-of-Envelope Calculations
| Metric |
Calculation |
Result |
| DAU |
600M MAU × 0.4 daily ratio |
240M DAU |
| Peak Concurrent |
240M DAU × 0.2 peak ratio |
48M concurrent |
| Bandwidth per User |
160 kbps avg quality |
20 KB/s per stream |
| Peak Bandwidth |
48M × 20 KB/s |
960 GB/s = 7.7 Tbps |
| Storage per Track |
3 qualities (96/160/320 kbps) × 3.5min avg |
~10 MB per track |
| Total Audio Storage |
100M tracks × 10 MB |
1 PB (+ replication) |
| Metadata DB |
100M tracks × 5KB metadata |
500 GB |
| User Activity Events |
240M DAU × 50 plays/day |
12B events/day = 140K/sec avg |
Interview Tip: Clarify premium vs free tier requirements. Free users get ads, lower quality, no offline. Premium gets 320kbps, offline, no ads. This affects CDN strategy and DRM requirements.
2. High-Level Design
flowchart TB
Mobile[Mobile Apps] --> LB[Load Balancer]
Web[Web Player] --> LB
Desktop[Desktop Apps] --> LB
LB --> API[API Gateway]
API --> Stream[Streaming Service]
API --> Catalog[Catalog Service]
API --> Search[Search Service]
API --> Playlist[Playlist Service]
API --> Rec[Recommendation Engine]
Stream --> CDN[CDN Edge Servers]
Catalog --> MetaDB[(Metadata DB)]
Search --> ES[(Elasticsearch)]
Rec --> ML[ML Models]
Rec --> FeatureStore[(Feature Store)]
Component Responsibilities
| Component |
Responsibility |
Technology |
| API Gateway |
Auth, rate limiting, routing, protocol translation |
Kong/Envoy |
| Streaming Service |
Generate playback URLs, handle quality selection, track position |
Go/Java |
| Catalog Service |
Manage tracks, albums, artists metadata, licensing |
Java/Kotlin |
| Search Service |
Index and query tracks/artists/albums, autocomplete |
Python/Java + Elasticsearch |
| Playlist Service |
CRUD playlists, collaborative editing, versioning |
Java + Cassandra |
| Recommendation Engine |
Generate personalized playlists, similar tracks, radio |
Python + TensorFlow/PyTorch |
| CDN |
Serve audio files globally with low latency |
Cloudflare/Fastly/Akamai |
| Origin Storage |
Store master audio files and transcoded variants |
S3/GCS |
| Event Stream |
Capture user activity (plays, skips, likes) for analytics/ML |
Kafka |
Interview Tip: Separate metadata (catalog, playlists) from audio delivery. Metadata changes frequently, audio is immutable. Use different storage and caching strategies for each.
3. Audio Streaming
Audio Formats & Quality Levels
| Tier |
Bitrate |
Codec |
File Size (3.5min) |
Use Case |
| Low |
96 kbps |
Ogg Vorbis / AAC |
2.5 MB |
Free tier, poor network |
| Normal |
160 kbps |
Ogg Vorbis / AAC |
4.2 MB |
Default premium, mobile |
| High |
320 kbps |
Ogg Vorbis / AAC |
8.4 MB |
Premium desktop, audiophiles |
| Lossless |
~1411 kbps |
FLAC |
37 MB |
HiFi tier (optional) |
Adaptive Bitrate Streaming
| Network Condition |
Detection |
Action |
Buffer Strategy |
| Excellent (4G/5G/WiFi) |
>2 Mbps, RTT <50ms |
Stream 320 kbps |
Prefetch 30s ahead |
| Good (3G/4G) |
500 Kbps - 2 Mbps |
Stream 160 kbps |
Prefetch 20s ahead |
| Poor (2G/slow 3G) |
<500 Kbps, packet loss >5% |
Stream 96 kbps |
Prefetch 10s ahead |
| Offline |
No connectivity |
Use cached/downloaded files |
Local playback |
Client Streaming Flow
sequenceDiagram
participant Client
participant API
participant Streaming
participant CDN
participant Origin
Client->>API: Request track (track_id, quality_pref)
API->>Streaming: Get playback URL
Streaming->>Streaming: Check user tier, location
Streaming->>Streaming: Select quality (96/160/320)
Streaming->>CDN: Generate signed URL (30min TTL)
Streaming-->>Client: Return CDN URL + metadata
loop Audio Chunks
Client->>CDN: GET /track/abc123/320kbps/chunk-001.ogg
alt Cache Hit
CDN-->>Client: Audio chunk (2s)
else Cache Miss
CDN->>Origin: Fetch chunk
Origin-->>CDN: Audio chunk
CDN-->>Client: Audio chunk
end
end
Client->>API: Report playback position (every 10s)
Client->>API: Send analytics event (play, skip, etc)
Buffer Management & Gapless Playback
| Feature |
Implementation |
Benefit |
| Chunked Delivery |
Split tracks into 2-5s chunks, HTTP range requests |
Fast start, adaptive switching, efficient caching |
| Buffer Target |
Maintain 10-30s buffer (network dependent) |
Smooth playback, handle jitter |
| Gapless Playback |
Preload next track final 10s, seamless transition |
Album/playlist continuity |
| Smart Prefetch |
Predict next 2-3 tracks (playlist order, shuffle), prefetch |
Zero playback delay on next |
| Quality Switch |
Switch at chunk boundary, no rebuffer |
Adapt to network without interruption |
| Seek Optimization |
Discard buffer, fetch target chunk + 5s ahead |
Fast seek response |
Interview Tip: Explain adaptive bitrate selection. Client measures throughput and RTT every 5-10s. If buffer drains faster than refill, downgrade quality. If buffer stays full and bandwidth available, upgrade. Avoid oscillation with hysteresis.
4. Content Delivery Network
CDN Strategy
| Component |
Configuration |
Rationale |
| Edge POPs |
200+ global POPs, route users to nearest |
Minimize RTT, maximize throughput |
| Cache TTL |
Audio files: 30 days, Metadata: 5 minutes |
Audio immutable, metadata changes frequently |
| Cache Hit Ratio |
Target 95%+ for top 1M tracks |
Most plays concentrate on popular tracks |
| Origin Shield |
Regional cache layer between edge and origin |
Reduce origin load, improve cache efficiency |
| URL Signing |
HMAC-signed URLs, 30min expiry, IP binding |
Prevent hotlinking, enforce auth |
| Chunk Size |
2-5s audio chunks (40-200 KB) |
Balance cache granularity vs overhead |
Audio File Storage Structure
| Path Pattern |
Example |
Purpose |
| /audio/{quality}/{track_id}/ |
/audio/320kbps/7a8f2b3c/ |
Organize by quality tier |
| chunk-{seq}.ogg |
chunk-001.ogg, chunk-002.ogg |
Sequential chunks for streaming |
| /images/album/{album_id}/ |
/images/album/xyz123/cover-640.jpg |
Album artwork (multiple sizes) |
| /images/artist/{artist_id}/ |
/images/artist/abc456/photo-1280.jpg |
Artist photos |
Prefetching & Caching Strategy
flowchart TD
Start[User presses play] --> CheckCache{Track in
local cache?}
CheckCache -->|Yes| PlayLocal[Play from cache]
CheckCache -->|No| RequestCDN[Request from CDN]
RequestCDN --> GetChunk[Get first 3 chunks]
GetChunk --> StartPlay[Start playback]
StartPlay --> BufferLoop[Buffer management loop]
BufferLoop --> CheckBuffer{Buffer < 10s?}
CheckBuffer -->|Yes| FetchMore[Fetch next 5 chunks]
CheckBuffer -->|No| CheckNext{Last 20s
of track?}
FetchMore --> BufferLoop
CheckNext -->|Yes| PrefetchNext[Prefetch next track
first 3 chunks]
CheckNext -->|No| BufferLoop
PrefetchNext --> BufferLoop
BufferLoop --> CheckEnd{Track
ended?}
CheckEnd -->|No| BufferLoop
CheckEnd -->|Yes| NextTrack[Load next track]
NextTrack --> CheckCache
Cache Optimization by Popularity
| Track Tier |
Criteria |
Cache Strategy |
Cache Hit Rate |
| Hot Tracks |
Top 10K tracks, >100K plays/day |
Cache at all edge POPs, never evict |
99.9% |
| Popular |
Top 1M tracks, >1K plays/day |
Cache at regional POPs, LRU eviction |
98% |
| Niche |
1M-10M tracks, >10 plays/day |
Cache on demand, aggressive LRU |
80% |
| Long Tail |
90M tracks, <10 plays/day |
Origin fetch, cache 1 hour |
30% |
Interview Tip: Explain origin shield pattern. Edge POPs cache from regional shield instead of origin. If 100 edge POPs have cache miss for same track, only 1 request hits origin (shield serves rest). Reduces origin load 100x.
5. Catalog & Metadata
Data Model
| Entity |
Key Attributes |
Storage |
Volume |
| Track |
id, title, duration, album_id, artist_ids[], genre, ISRC, release_date |
PostgreSQL |
100M records |
| Album |
id, title, artist_id, release_date, label, cover_art_url, UPC |
PostgreSQL |
4M records |
| Artist |
id, name, bio, genres[], verified, follower_count, photo_url |
PostgreSQL |
8M records |
| Genre |
id, name, parent_genre_id |
PostgreSQL |
5K records |
| AudioFile |
track_id, quality, codec, bitrate, file_path, checksum |
PostgreSQL |
300M records (3 per track) |
| License |
track_id, territory[], start_date, end_date, rights_holder |
PostgreSQL |
500M records |
Schema (Simplified)
| Table |
Schema |
| tracks |
id (PK), title, duration_ms, album_id (FK), isrc, explicit, popularity_score, created_at
|
| albums |
id (PK), title, album_type (album/single/compilation), release_date, label, upc, created_at
|
| artists |
id (PK), name, verified, follower_count, monthly_listeners, created_at
|
| track_artists |
track_id (FK), artist_id (FK), role (primary/featured), order
|
| album_artists |
album_id (FK), artist_id (FK), role, order
|
| audio_files |
id (PK), track_id (FK), quality (96/160/320), codec, file_path, size_bytes, checksum
|
| licenses |
id (PK), track_id (FK), territory, start_date, end_date, rights_holder, license_type
|
Caching Strategy
| Data Type |
Cache Key |
TTL |
Invalidation |
| Track Metadata |
track:{track_id} |
1 hour |
On update, delete key |
| Album |
album:{album_id} |
1 hour |
On update, delete key |
| Artist |
artist:{artist_id} |
30 min |
Follower count updates frequently |
| Album Tracks |
album:{album_id}:tracks |
6 hours |
Rarely changes after release |
| Artist Top Tracks |
artist:{artist_id}:top |
1 hour |
Recompute daily batch |
| License Check |
license:{track_id}:{territory} |
1 day |
Licenses change infrequently |
Catalog Updates
| Update Type |
Frequency |
Process |
| New Releases |
~50K tracks/day |
Ingest pipeline: validate metadata, transcode audio, update DB, index search |
| Metadata Correction |
~10K updates/day |
Update DB, invalidate cache, reindex search |
| Popularity Scores |
Daily batch |
Compute from last 28 days play counts, update DB |
| License Changes |
Ad-hoc |
Update license table, invalidate cache, block playback if expired |
Interview Tip: Discuss sharding strategy for 100M tracks. Shard by track_id hash across 64 PostgreSQL shards. Keep related data (track, album, artist) on same shard where possible. Use consistent hashing for adding shards.
6. Search
Search Index Architecture
| Index Type |
Documents |
Fields Indexed |
Shards |
| Tracks |
100M |
title, artist_names, album_title, isrc, lyrics |
128 shards |
| Albums |
4M |
title, artist_names, upc, label |
8 shards |
| Artists |
8M |
name, bio, genres |
16 shards |
| Playlists |
5B (user + algorithmic) |
title, description, creator_name (public only) |
256 shards |
Search Features
| Feature |
Implementation |
Example |
| Fuzzy Matching |
Levenshtein distance ≤2, handle typos |
"Bettles" → "Beatles" |
| Autocomplete |
Edge n-gram tokenizer, prefix queries |
"shake it" → "Shake It Off" |
| Synonyms |
Synonym filter (feat/featuring, ft., &) |
"Dua Lipa ft. Calvin Harris" |
| Boosting |
Popularity score × 10, recent releases × 2 |
Popular tracks rank higher |
| Multi-Match |
Search across title, artist, album with field weights |
title^3, artist^2, album^1 |
| Filters |
Genre, year, explicit, duration, availability |
genre:rock AND year:[2020 TO 2024] |
| Personalization |
Boost tracks from user's top genres/artists |
User loves jazz → jazz results rank higher |
Autocomplete Flow
sequenceDiagram
participant User
participant Client
participant SearchAPI
participant Redis
participant Elastic
User->>Client: Type "shake"
Client->>SearchAPI: GET /search/autocomplete?q=shake
SearchAPI->>Redis: Check cache (autocomplete:shake)
alt Cache Hit
Redis-->>SearchAPI: Cached results
else Cache Miss
SearchAPI->>Elastic: Prefix query on tracks/artists/albums
Elastic-->>SearchAPI: Top 10 results (popularity weighted)
SearchAPI->>Redis: Cache results (TTL 1 hour)
end
SearchAPI-->>Client: JSON results (titles, ids, types)
Client-->>User: Display dropdown suggestions
User->>Client: Select "Shake It Off - Taylor Swift"
Client->>SearchAPI: GET /tracks/{track_id}
SearchAPI-->>Client: Full track metadata
Client-->>User: Navigate to track page
Search Ranking Factors
| Factor |
Weight |
Calculation |
| Text Relevance |
1.0x |
BM25 score (Elasticsearch default) |
| Popularity |
10.0x |
log(play_count_28d + 1) |
| Recency |
2.0x |
Boost if released within last 90 days |
| User Affinity |
5.0x |
Boost if artist/genre in user's top 10 |
| Exact Match |
20.0x |
Title exactly matches query |
| Verified Artist |
1.5x |
Boost verified artists over similar names |
Index Update Strategy
| Event |
Trigger |
Update Process |
| New Track |
Catalog ingestion complete |
Index document immediately (async) |
| Metadata Edit |
Catalog service update |
Update document, refresh index (1s delay) |
| Popularity Score |
Daily batch (02:00 UTC) |
Bulk update all documents |
| License Expiry |
License check fails |
Set available=false, update index |
Interview Tip: Explain autocomplete optimization. Use edge n-grams for prefix matching (e.g., "shake" → "sh", "sha", "shak", "shake"). Cache top 1K queries in Redis. For long tail queries, hit Elasticsearch. Personalize results using user's listening history.
7. Personalization & Recommendations
Recommendation Types
| Feature |
Frequency |
Algorithm |
Input Signals |
| Discover Weekly |
Weekly (Monday) |
Collaborative filtering + audio features |
User listening history, similar users, audio analysis |
| Daily Mix (1-6) |
Daily |
Genre clustering + popularity |
User's top genres, recent plays, era preferences |
| Release Radar |
Weekly (Friday) |
Artist following + similar artists |
Followed artists, new releases, similar artists |
| Radio |
On-demand |
Content-based + collaborative |
Seed track audio features, user taste profile |
| Home Feed |
Real-time |
Contextual ranking |
Time of day, recent listening, trending, mood |
| Similar Tracks |
On-demand |
Audio embeddings similarity |
Audio features (tempo, key, energy, valence) |
Discover Weekly Pipeline
flowchart TD
subgraph Data Collection
UserPlays[User play events
last 6 months] --> DataLake[(Data Lake)]
AudioFeatures[Audio analysis
all tracks] --> FeatureStore[(Feature Store)]
end
subgraph Batch Processing
DataLake --> CollabFilter[Collaborative Filtering
Matrix Factorization]
DataLake --> UserClusters[User Clustering
K-means by taste]
FeatureStore --> ContentSim[Content Similarity
Audio embeddings]
CollabFilter --> CandidateGen[Candidate Generation
500 tracks per user]
UserClusters --> CandidateGen
ContentSim --> CandidateGen
end
subgraph Ranking
CandidateGen --> RankModel[Ranking Model
XGBoost]
UserProfile[User Profile
recent plays, skips] --> RankModel
RankModel --> Filter[Filter
remove heard/saved]
Filter --> Top30[Top 30 tracks]
end
subgraph Delivery
Top30 --> PlaylistGen[Generate Playlist
optimize order]
PlaylistGen --> Cassandra[(Cassandra
user playlists)]
Cassandra --> App[Spotify Apps]
end
Collaborative Filtering Approach
| Method |
Input |
Output |
Use Case |
| Matrix Factorization (ALS) |
User-track play matrix (600M × 100M) |
User embeddings (dim 200), track embeddings (dim 200) |
Find tracks liked by similar users |
| User-User CF |
User listening history |
Top 100 similar users (cosine similarity) |
Recommend tracks from similar users |
| Item-Item CF |
Track co-occurrence in playlists |
Similar tracks (Jaccard similarity) |
"Fans also like" suggestions |
Content-Based Features (Audio Analysis)
| Feature |
Range |
Description |
| Tempo |
40-200 BPM |
Beats per minute, detected via onset detection |
| Key |
0-11 (C to B) |
Musical key via chroma features |
| Energy |
0.0-1.0 |
Intensity, loudness, activity (0=calm, 1=intense) |
| Valence |
0.0-1.0 |
Mood positivity (0=sad, 1=happy) |
| Danceability |
0.0-1.0 |
Suitability for dancing (rhythm, tempo, beat strength) |
| Acousticness |
0.0-1.0 |
Confidence track is acoustic (no electric instruments) |
| Instrumentalness |
0.0-1.0 |
Likelihood of no vocals (>0.5 = instrumental) |
| Speechiness |
0.0-1.0 |
Presence of spoken words (>0.66 = podcast/audiobook) |
Ranking Model (XGBoost)
| Feature Category |
Features |
Importance |
| User-Track Affinity |
Embedding dot product, collaborative score, genre match |
35% |
| Track Popularity |
Play count (28d), skip rate, save rate, playlist adds |
25% |
| Audio Similarity |
Cosine similarity to user's top tracks (tempo, energy, valence) |
20% |
| Freshness |
Release date, user hasn't heard, new artist for user |
10% |
| Diversity |
Genre diversity, artist diversity, tempo variety |
10% |
Real-Time Personalization
| Context |
Signal |
Recommendation Adjustment |
| Time of Day |
Morning (6-10am) |
Boost upbeat, energetic tracks |
| Time of Day |
Evening (8pm-12am) |
Boost chill, low-energy tracks |
| Listening Mode |
Gym/workout playlist |
High tempo (140-180 BPM), high energy |
| Listening Mode |
Focus/study playlist |
Instrumental, low energy, acoustic |
| Recent Behavior |
5 consecutive skips |
Switch to different genre/style |
| Recent Behavior |
Replay same track 3+ times |
Recommend similar tracks immediately |
Interview Tip: Explain cold start problem. New users: recommend popular tracks by country/age. New tracks: recommend to users who like the artist or similar audio features. Use content-based methods (audio features) when collaborative filtering fails.
8. Playlists
Playlist Types
| Type |
Owner |
Update Frequency |
Example |
| User Playlist |
Single user |
On-demand |
"My Favorites", "Workout Mix" |
| Collaborative Playlist |
Multiple users |
On-demand |
"Road Trip 2024" |
| Algorithmic Playlist |
Spotify (personalized) |
Daily/Weekly |
"Discover Weekly", "Daily Mix 1-6" |
| Editorial Playlist |
Spotify curators |
Weekly |
"Today's Top Hits", "RapCaviar" |
| Artist Playlist |
Artist |
Monthly |
"This Is Taylor Swift" |
Playlist Schema (Cassandra)
| Table |
Partition Key |
Clustering Key |
Columns |
| playlists |
playlist_id |
- |
title, description, owner_id, collaborative, public, created_at, updated_at, follower_count, total_duration |
| playlist_tracks |
playlist_id |
position (ASC) |
track_id, added_by_user_id, added_at |
| user_playlists |
user_id |
updated_at (DESC) |
playlist_id, title, track_count |
| playlist_followers |
playlist_id |
user_id |
followed_at |
| playlist_versions |
playlist_id |
version (DESC) |
snapshot_id, track_ids[], updated_at |
Collaborative Playlist Operations
| Operation |
Flow |
Conflict Resolution |
| Add Track |
INSERT into playlist_tracks with position = max(position) + 1 |
Append only, no conflict |
| Remove Track |
DELETE from playlist_tracks WHERE position = X, reorder remaining |
If concurrent delete, idempotent |
| Reorder Track |
Update positions of affected range |
Last write wins (LWW) with timestamp |
| Concurrent Edits |
Use version/snapshot_id, optimistic locking |
Retry with latest version if conflict |
| Real-Time Sync |
Publish change to WebSocket/SSE, push to collaborators |
Show "User X added Y" notification |
Algorithmic Playlist Generation
| Playlist |
Generation Logic |
Track Count |
| Discover Weekly |
Collaborative filtering + audio features, filter heard tracks |
30 tracks |
| Daily Mix 1-6 |
Cluster user's tracks by genre/era, pick top tracks per cluster |
50 tracks each |
| Release Radar |
New releases from followed artists + similar artists (last 7 days) |
40 tracks |
| On Repeat |
Top 30 most played tracks (last 28 days), sort by play count |
30 tracks |
| Repeat Rewind |
Top tracks from 1+ year ago, nostalgic picks |
30 tracks |
| Time Capsule |
Tracks popular during user's youth (age-based), nostalgia scoring |
50 tracks |
Playlist Versioning
sequenceDiagram
participant User1
participant User2
participant PlaylistAPI
participant Cassandra
participant WebSocket
User1->>PlaylistAPI: Add track (playlist_id, track_id, position)
PlaylistAPI->>Cassandra: Check current version (v5)
PlaylistAPI->>Cassandra: INSERT track, UPDATE version to v6
PlaylistAPI->>WebSocket: Broadcast change to subscribers
WebSocket-->>User2: "User1 added track X"
User2->>PlaylistAPI: Remove track (playlist_id, position=10)
PlaylistAPI->>Cassandra: Check current version (v6)
PlaylistAPI->>Cassandra: DELETE track, UPDATE version to v7
PlaylistAPI->>WebSocket: Broadcast change
WebSocket-->>User1: "User2 removed track Y"
Interview Tip: Discuss playlist snapshot API. Each edit creates new version. External apps (integrations) hold snapshot_id. If playlist changes, snapshot_id becomes stale. Apps must refresh to get latest version. Prevents race conditions in collaborative editing.
9. Offline Download & Sync
Offline Download Flow
sequenceDiagram
participant User
participant App
participant DownloadService
participant DRM
participant CDN
participant LocalDB
User->>App: Download playlist/album
App->>DownloadService: Queue download (tracks[], quality)
DownloadService->>DRM: Request license (track_ids[], device_id)
DRM->>DRM: Check user tier, device limit (5 devices)
DRM-->>DownloadService: Issue license (encrypted key, 30 days TTL)
loop For each track
DownloadService->>CDN: Download encrypted audio file
CDN-->>DownloadService: Encrypted .ogg chunks
DownloadService->>LocalDB: Store encrypted file + license
end
DownloadService-->>App: Download complete
App-->>User: "Available offline"
Note over User,LocalDB: Offline playback
User->>App: Play offline track
App->>LocalDB: Load encrypted file + license
App->>DRM: Decrypt with license key
DRM-->>App: Decrypted audio stream
App-->>User: Playback
DRM & License Management
| Aspect |
Implementation |
Details |
| DRM System |
Widevine (Android), FairPlay (iOS), PlayReady (Windows) |
Platform-native DRM for secure decryption |
| Encryption |
AES-128-CBC per track, unique key per track |
Keys stored in license server, never on device |
| License Duration |
30 days offline, renewable while online |
Force re-auth to check subscription status |
| Device Limit |
Max 5 devices per account, revocable |
Tracked in user_devices table |
| Download Limit |
10K tracks per device (free: 0, premium: 10K) |
Prevent abuse, manage storage |
| License Renewal |
Auto-renew when app connects online |
Check subscription active, extend TTL |
| Revocation |
Server-side license revoke on subscription cancel |
Offline tracks unplayable after license expires |
Sync Queue Management
| Priority |
Condition |
Behavior |
| High |
User explicitly downloads playlist/album |
Download immediately, show progress |
| Medium |
Auto-download "On Repeat" (user setting enabled) |
Download on WiFi, low battery OK |
| Low |
Prefetch next 5 tracks in queue (proactive) |
Download on WiFi, don't drain battery |
| Paused |
Cellular connection, "Download on WiFi only" enabled |
Queue downloads, resume when WiFi detected |
| Canceled |
Storage full (local device capacity) |
Show error, prompt user to free space |
Offline Playlist Sync
| Scenario |
Server Change |
Client Sync Action |
| Tracks Added |
User adds 5 new tracks to offline playlist (on web) |
Download new tracks on next WiFi connection |
| Tracks Removed |
User removes tracks from playlist |
Delete local files, free storage |
| Track Unavailable |
Licensing change, track removed from catalog |
Delete local file, remove from offline playlist |
| Playlist Deleted |
User deletes playlist on another device |
Delete all local files for that playlist |
| Quality Upgrade |
User upgrades from 160 kbps to 320 kbps setting |
Re-download higher quality, delete old files |
Offline Storage Schema (SQLite)
| Table |
Columns |
Purpose |
| offline_tracks |
track_id (PK), file_path, quality, size_bytes, downloaded_at, license_expires_at |
Track downloaded status |
| offline_playlists |
playlist_id (PK), title, snapshot_id, synced_at |
Offline playlist metadata |
| download_queue |
id (PK), track_id, priority, status (pending/in_progress/completed/failed), retries |
Download task queue |
| licenses |
track_id (PK), license_key (encrypted), issued_at, expires_at, device_id |
DRM license cache |
Interview Tip: Explain subscription enforcement. When user cancels premium, license server stops issuing renewals. Existing licenses expire in 30 days. After expiry, offline playback fails (license check fails). This enforces subscription requirement without online check during playback.
10. Scalability & Performance
CDN Cache Performance
| Metric |
Target |
Current |
Impact |
| Cache Hit Ratio (top 1M tracks) |
95% |
97% |
97% of requests served from edge, 3% origin fetch |
| Cache Hit Ratio (all tracks) |
85% |
88% |
12% long tail tracks hit origin |
| Origin Bandwidth |
<5% of total |
3.2% |
Origin handles ~300 Gbps vs 10 Tbps total |
| P50 TTFB (edge) |
<50ms |
35ms |
Fast playback start |
| P99 TTFB (edge) |
<200ms |
150ms |
Consistent experience globally |
Metadata Caching
| Cache Layer |
Technology |
Hit Ratio |
Latency |
| L1: Application Cache |
In-memory (local to service instance) |
60% |
<1ms |
| L2: Redis Cluster |
Distributed cache (16 shards) |
35% |
1-5ms |
| L3: PostgreSQL Replica |
Read replica (read-only queries) |
5% |
10-50ms |
| Combined Hit Ratio |
- |
~95% |
Avg ~2ms |
Event Streaming (Kafka)
| Event Type |
Volume |
Partitions |
Consumers |
| Play Events |
10B/day (120K/sec avg) |
256 partitions (by user_id hash) |
Analytics, Recommendations, Royalties |
| Skip Events |
2B/day (24K/sec avg) |
128 partitions |
Analytics, Recommendations |
| Save/Like Events |
500M/day (6K/sec avg) |
64 partitions |
User Service, Recommendations |
| Playlist Changes |
200M/day (2.5K/sec avg) |
32 partitions |
Playlist Service, Search Indexer |
| Search Queries |
1B/day (12K/sec avg) |
64 partitions |
Analytics, Search Ranking |
Database Sharding
| Database |
Sharding Key |
Shards |
Data per Shard |
| PostgreSQL (Catalog) |
track_id hash |
64 shards |
~1.5M tracks each |
| Cassandra (User Data) |
user_id hash |
128 shards |
~5M users each |
| Cassandra (Playlists) |
playlist_id hash |
256 shards |
~20M playlists each |
Auto-Scaling Strategy
| Service |
Scale Metric |
Min Instances |
Max Instances |
| API Gateway |
CPU >70% OR RPS >10K |
50 |
500 |
| Streaming Service |
Active streams >1M per instance |
100 |
1000 |
| Catalog Service |
CPU >60% OR p99 latency >100ms |
20 |
200 |
| Search Service |
Query queue depth >1000 |
30 |
300 |
| Recommendation Engine |
Request rate (scheduled scaling, peak hours) |
10 |
50 |
Cost Optimization
| Category |
Monthly Cost |
Optimization Strategy |
| CDN Bandwidth |
$50M |
Negotiate volume discounts, optimize bitrates, tiered quality by user |
| Storage (audio) |
$10M |
S3 Intelligent-Tiering, delete unused variants, dedup identical files |
| Compute (streaming) |
$15M |
Spot instances, right-size instance types, scale to zero off-peak |
| Databases |
$8M |
Reserved instances, vertical scaling, cache layer to reduce queries |
| ML/Recommendations |
$5M |
Batch processing off-peak, optimize model inference, feature store caching |
| Total |
$88M/month |
~$0.15 per MAU |
Monitoring & Alerts
| Metric |
Threshold |
Severity |
Action |
| Playback Start Failure Rate |
>1% |
P1 |
Page on-call, investigate CDN/origin |
| API Error Rate |
>0.5% |
P2 |
Alert team, check service health |
| Search Latency p99 |
>500ms |
P3 |
Scale Elasticsearch cluster |
| Cache Hit Ratio |
<80% |
P3 |
Review cache config, increase TTL |
| Kafka Consumer Lag |
>1M messages |
P2 |
Scale consumer group, check processing |
| DB Connection Pool |
>80% utilization |
P2 |
Increase pool size or scale DB |
Interview Tip: Discuss peak load handling. Friday evenings = new releases + commute time = 2x normal traffic. Use auto-scaling, CDN pre-warming (push hot releases to edge), and over-provision by 20% during peak windows. Monitor real-time dashboards to catch issues early.
System Design Summary
| Aspect |
Key Decisions |
Rationale |
| Audio Delivery |
CDN-first, chunked streaming, adaptive bitrate |
Low latency, high cache hit ratio, smooth playback |
| Catalog Storage |
PostgreSQL (sharded), Redis caching, Elasticsearch indexing |
Relational data, fast queries, powerful search |
| Personalization |
Batch collaborative filtering + real-time ranking |
Balance freshness vs compute cost |
| Playlists |
Cassandra, versioning, WebSocket sync |
High write throughput, collaborative editing |
| Offline Mode |
Widevine DRM, 30-day licenses, sync queue |
Protect content, enforce subscriptions |
| Scalability |
Auto-scaling, sharding, event streaming (Kafka) |
Handle 600M MAU, 50M concurrent streams |