Full Stack • Java • System Design • Cloud • AI Engineering

Spotify System Design — 1 Hour Interview Guide

Design a scalable music streaming platform like Spotify. Covers requirements, capacity estimation, audio streaming pipeline, OGG Vorbis encoding, CDN strategy, search, Discover Weekly recommendation engine, database design, caching, offline DRM, and real-time Spotify Connect — all in a 1-hour interview format.

1-Hour Interview Roadmap

Time Topic
0 – 5 min Requirements clarification
5 – 10 min Capacity estimation
10 – 18 min High-level architecture + core components
18 – 28 min Audio upload pipeline + encoding
28 – 36 min Audio streaming + CDN strategy
36 – 46 min Search + Discover Weekly recommendation engine
46 – 54 min Database design + caching
54 – 58 min Scaling + offline playback + social features
58 – 60 min Trade-offs + failure scenarios

What Are We Building?

A music and podcast streaming platform where users can:

  • Stream millions of songs and podcasts on demand
  • Search by artist, album, song, genre, or mood
  • Create, share, and follow playlists
  • Get personalized music recommendations (Discover Weekly, Daily Mixes)
  • Download tracks for offline playback
  • Follow artists and friends, see what they're listening to
  • See real-time "Now Playing" across all connected devices

Scale reference: Spotify has 602 million monthly active users, 240 million paid subscribers, 100 million+ tracks in catalog, and streams audio to 80 million concurrent listeners at peak.

Key difference from YouTube/Netflix: Spotify streams audio only — files are 3–10 MB vs 2 GB for video. This makes CDN caching far more aggressive. The real hard challenge is the recommendation engine — Spotify's Discover Weekly is widely considered the best music recommendation system ever built.


Step 1 — Requirements

Functional Requirements

# Requirement
1 Users can search for songs, albums, artists, playlists, and podcasts
2 Users can stream any track on demand with < 250ms playback start time
3 Users can create, edit, and share playlists
4 Users can follow artists, friends, and public playlists
5 Free users hear ads between tracks; Premium users stream ad-free
6 Premium users can download tracks for offline listening
7 Users receive personalized recommendations — Discover Weekly, Daily Mixes
8 Users see real-time "Now Playing" on their profile
9 Artists can upload tracks and view play count and listener analytics
10 Multiple devices: phone, desktop, web, TV, car, speaker — seamless handoff

Non-Functional Requirements

# Requirement
1 Audio playback starts within 250ms — fast buffering, no startup lag
2 No buffering interruptions on 1 Mbps+ connections
3 High availability — 99.99% uptime
4 Search results returned within 200ms
5 Recommendations refreshed weekly (Discover Weekly) and daily (Daily Mixes)
6 Offline content must remain playable up to 30 days after download
7 Eventual consistency acceptable for play counts, follower counts, playlists
8 Strong consistency required for subscription status and payment

Out of Scope

  • Live audio and concert streaming
  • Payment and billing infrastructure
  • Podcast recording tools
  • Artist royalty calculation engine
  • Advertising platform

Step 2 — Capacity Estimation

Assumptions

Metric Value
Monthly Active Users (MAU) 600 million
Daily Active Users (DAU) 250 million
Peak concurrent listeners 80 million
Total tracks in catalog 100 million
Average track size (128 Kbps MP3) 3.5 MB
Average track size (320 Kbps MP3) 8.5 MB (Premium)
Average track size (OGG Vorbis HQ) 6 MB
Average listen time per user/day 30 minutes
Tracks played per user/day ~6 songs
New tracks uploaded per day ~60,000

Storage Estimation

Catalog:          100M tracks × 3 formats × 6 MB avg  = ~1.8 PB total audio storage
New daily:        60K tracks × 3 formats × 6 MB        = ~1 TB/day

Podcast episodes: ~5M episodes × 50 MB avg             = ~250 TB

Streaming Bandwidth at Peak

80M concurrent streams × 128 Kbps (free) or 320 Kbps (premium)
Average: ~200 Kbps across user base

80M × 200 Kbps = 16 Tbps peak outbound bandwidth

CDN absorbs ~90%  →  origin bandwidth ≈ 1.6 Tbps

Search and API Traffic

250M DAU × 5 searches/day = 1.25 billion searches/day
= ~14,500 searches/second (sustained)
= ~50,000 searches/second (peak)

Key Insight

Spotify is fundamentally different from Netflix and YouTube. Audio files are small and immutable (3–10 MB vs gigabytes for video). The entire catalog (~1.8 PB) can potentially be cached at CDN edge nodes. The harder challenge is not delivery — it is the recommendation engine that generates 40+ personalized playlists per user per week.


Step 3 — High-Level Architecture

flowchart TD
    Client[Client\nMobile / Desktop / Web] --> AG[API Gateway\nAuth + Rate Limiting]

    AG --> SS[Stream Service]
    AG --> SRCH[Search Service]
    AG --> REC[Recommendation Service]
    AG --> PLS[Playlist Service]
    AG --> USER[User Service]
    AG --> ART[Artist Service]

    SS --> CDN[CDN\nAudio Segments]
    CDN -->|cache miss| AS[Audio Storage\nS3]

    ART --> KF[Kafka\ntrack.uploaded]
    KF --> ENC[Encoder Service\nFFmpeg]
    ENC --> AS
    KF --> IDX[Search Indexer\nElasticsearch]
    KF --> RC[Recommendation\nPipeline\nSpark + TF]

    USER --> UDB[(PostgreSQL\nUser Profiles)]
    PLS --> PDB[(Cassandra\nPlaylists)]
    REC --> RDB[(Redis\nRec Cache)]
    SRCH --> ES[(Elasticsearch\nMusic Index)]

    AG --> NP[Now Playing\nService]
    NP --> WS[WebSocket\nReal-time]
    NP --> NPDB[(Redis\nNow Playing State)]

Component Responsibilities

Component Responsibility
API Gateway JWT auth, plan check (Free vs Premium), rate limiting, routing
Stream Service Generate pre-signed CDN URLs for audio segments; track play events
Encoder Service Transcode uploaded audio into multiple quality tiers; generate waveform data
Search Service Full-text search over tracks, artists, albums, playlists via Elasticsearch
Recommendation Svc Serve pre-computed personalized playlists; Discover Weekly, Daily Mix
Playlist Service CRUD for user playlists; collaborative playlist support
User Service Profiles, follows, subscription status, preferences
Artist Service Track upload, analytics, album management
Now Playing Service Real-time "what is this user listening to" — WebSocket state sync
CDN Global edge delivery of audio files — cache most of the catalog

Step 4 — Audio Upload and Encoding Pipeline

Upload Flow (Artist Side)

sequenceDiagram
    participant Artist
    participant ART as Artist Service
    participant MDB as Metadata DB (PostgreSQL)
    participant S3 as Raw S3
    participant KF as Kafka
    participant ENC as Encoder Service
    participant S3A as Processed S3

    Artist->>ART: Upload track {title, artist, album, genre, tags}
    ART->>MDB: INSERT track {status: PROCESSING}
    MDB-->>ART: track_id
    ART->>S3: Generate pre-signed URL
    ART-->>Artist: {track_id, upload_url}

    Artist->>S3: PUT raw audio file (WAV/FLAC/MP3)
    S3-->>Artist: Upload complete

    Artist->>ART: POST /tracks/{track_id}/complete
    ART->>KF: Publish track.uploaded {track_id, raw_s3_key}

    KF->>ENC: Consume event
    ENC->>S3: Download raw file
    ENC->>ENC: Encode to 4 quality tiers
    ENC->>S3A: Store encoded files
    ENC->>MDB: UPDATE status = LIVE, set streaming_urls
    ENC->>KF: Publish track.ready {track_id}

Audio Encoding — Quality Tiers

flowchart LR
    RAW[Raw Audio\nWAV / FLAC] --> ENC[Encoder\nFFmpeg]
    ENC --> T1[OGG Vorbis\n96 Kbps\nFree / Low Data]
    ENC --> T2[OGG Vorbis\n160 Kbps\nFree / Normal]
    ENC --> T3[OGG Vorbis\n320 Kbps\nPremium]
    ENC --> T4[AAC\n256 Kbps\nApple devices]
    ENC --> WV[Waveform JSON\nfor scrubber visualization]
    T1 & T2 & T3 & T4 --> S3[Processed S3]
    S3 --> CDN[CDN Edge]
Quality Tier Format Bitrate Users Served File Size (3 min)
Low OGG Vorbis 96 Kbps Free on low data ~2 MB
Normal OGG Vorbis 160 Kbps Free (default) ~3.5 MB
High OGG Vorbis 320 Kbps Premium ~7.2 MB
Very High (HiFi) AAC / FLAC 256 Kbps+ Premium / Spotify HiFi ~6 MB

Why OGG Vorbis (Not MP3)?

Spotify chose OGG Vorbis as its primary codec because:

  • Better quality-per-bit than MP3 at the same bitrate
  • Royalty-free — no licensing fees per stream (unlike MP3)
  • Better suited for streaming — gapless playback, no encoder delay

Step 5 — Audio Streaming Flow

What Happens When You Press Play

sequenceDiagram
    participant Client
    participant AG as API Gateway
    participant SS as Stream Service
    participant CDN

    Client->>AG: GET /tracks/{track_id}/stream
    AG->>AG: Check JWT + subscription plan
    AG->>SS: Forward request

    SS->>SS: Determine quality tier\n(Free=160Kbps, Premium=320Kbps)
    SS->>SS: Generate pre-signed CDN URL\n(expires in 60 seconds)
    SS-->>Client: {stream_url: "cdn.spotify.com/t/abc.ogg?sig=...", expires_at: ...}

    Client->>CDN: GET cdn.spotify.com/t/abc.ogg
    CDN-->>Client: Stream audio bytes (range requests)

    Client->>AG: POST /events/play {track_id, position_ms, context}
    Note over Client,AG: Fire-and-forget play event → Kafka → analytics

Chunked Streaming with HTTP Range Requests

Spotify does not download the full audio file before playback. It uses HTTP range requests to stream in chunks:

Client requests:
  GET /audio/track_abc.ogg HTTP/1.1
  Range: bytes=0-131071           ← first 128 KB

Server responds:
  HTTP/1.1 206 Partial Content
  Content-Range: bytes 0-131071/7340032
  [128 KB of audio data]

Player buffers 30 seconds ahead, then requests next chunk:
  Range: bytes=131072-262143      ← next 128 KB

Benefits:

  • Playback starts after buffering just the first chunk (~1 second of audio)
  • Seeking jumps directly to the byte offset for the target timestamp
  • No wasted bandwidth if the user skips a track after 5 seconds

CDN Strategy for Audio

Audio files are small and immutable — a track's bytes never change after encoding. This makes them ideal for aggressive CDN caching:

flowchart LR
    Client -->|GET track_abc_320.ogg| CDN_EDGE[CDN Edge\nNearest PoP]
    CDN_EDGE -->|Cache HIT ~90%| Client
    CDN_EDGE -->|Cache MISS| S3[Processed S3]
    S3 --> CDN_EDGE
    CDN_EDGE --> Client
Content CDN TTL Reasoning
Audio files 1 year Immutable — bytes never change after encoding
Album artwork 30 days Rarely updated
Waveform JSON 7 days Computed once; occasionally updated
Track metadata 1 hour Play counts change; title/artist rarely change

Top 10% of Spotify's catalog = ~90% of all streams (Pareto distribution). Top 10M tracks × 6 MB avg = ~60 TB — fully cacheable at major CDN PoPs.


Search Requirements

  • Search by song title, artist name, album name, genre, mood, playlist name
  • Autocomplete while typing
  • Results ranked by relevance and popularity
  • Filter by content type (track, album, artist, playlist, podcast)
  • Results returned within 200ms

Search Architecture

flowchart LR
    KF[Kafka\ntrack.ready\nartist.updated] --> IDX[Search Indexer]
    IDX --> ES[Elasticsearch\nMusic Index]

    Client -->|"Bohemian Rhapsody"| SRCH[Search Service]
    SRCH -->|multi-match query| ES
    ES -->|Top 50 results| SRCH
    SRCH -->|Enrich + personalize rank| RDB[(Redis\nUser Context)]
    SRCH --> Client

Elasticsearch Track Document

{
  "track_id":      "4u7EnebtmKWzUH433cf5Qv",
  "title":         "Bohemian Rhapsody",
  "title_suggest": "Bohemian Rhapsody",
  "artist":        "Queen",
  "album":         "A Night at the Opera",
  "genres":        ["rock", "classic rock", "arena rock"],
  "mood_tags":     ["epic", "emotional", "dramatic"],
  "release_year":  1975,
  "duration_ms":   354000,
  "play_count":    2800000000,
  "popularity":    98,
  "language":      "en",
  "is_explicit":   false
}

Search Ranking Formula

score = BM25(query_text, title, artist, album)
      × log(play_count + 1)          ← global popularity boost
      × genre_affinity(user, track)  ← personalized boost
      × recency_factor               ← new releases boosted

Personalized ranking matters: a metal fan searching "queen" gets Queen the band; a chess fan needs disambiguation. Spotify knows your genre preferences from listening history.


Step 7 — Recommendation Engine (Discover Weekly)

Why Recommendations Are Spotify's Core Differentiator

Spotify's Discover Weekly playlist — 30 songs every Monday, personalized per user — has been described as the best recommendation system ever built. 40 million users listen to it every week. It drives more engagement than any other feature.

Three Types of Signals Spotify Uses

Signal Type What It Is Example
Collaborative filtering Users who listened to what you listened to also liked... You like Radiohead → recommend Thom Yorke solo
Content-based filtering Audio feature analysis of tracks you love Tempo, key, valence, energy, danceability
NLP on playlist names Mine playlist titles and descriptions for mood context "sad rainy day" playlist → detect mood context

Audio Feature Analysis

Spotify's Echo Nest (acquired 2014) analyzes raw audio and extracts numerical features for every track:

{
  "tempo":            120.0,   ← BPM
  "energy":           0.85,    ← intensity (0.0–1.0)
  "valence":          0.32,    ← musical positivity (0.0–1.0)
  "danceability":     0.71,    ← rhythm strength
  "acousticness":     0.12,    ← acoustic vs electronic
  "instrumentalness": 0.0,     ← vocals present?
  "loudness":         -5.2,    ← dB
  "key":              5,       ← musical key (C=0, C#=1...)
  "mode":             1        ← major (1) or minor (0)
}

Tracks with similar feature vectors are "sonically similar" — this enables mood-based recommendations even for brand new tracks with no play history.

Recommendation Pipeline Architecture

flowchart TD
    PH[Play History\nLikes, Skips\nPlaylist additions] --> KF[Kafka\nListen Events]

    KF --> BATCH[Offline Batch\nApache Spark\nweekly training]
    KF --> STREAM[Online Stream\nReal-time signals]

    BATCH -->|User-track\nembedding vectors| FAISS[(FAISS\nVector Store)]
    BATCH -->|Collaborative filter\nmatrix factorization| CF[(CF Model Store)]
    STREAM -->|Recent context| RC[(Redis\nContext Cache)]

    User -->|Monday morning| RS[Recommendation Service]
    RS -->|User vector lookup| FAISS
    FAISS -->|500 candidate tracks| RANK[Ranking Model\nNeural Net]
    RANK -->|Context| RC
    RANK -->|Top 30 ranked tracks| DW[Discover Weekly\nPlaylist]
    DW --> PDB[(Cassandra\nPlaylist Store)]
    DW --> RC2[(Redis\nPlaylist Cache)]

Discover Weekly Generation — Weekly Batch

Every Sunday night (low traffic):
  1. For each of 600M users:
     a. Build user embedding from last 2 weeks of listens
     b. Find 500 nearest tracks using FAISS ANN search
     c. Filter out: already heard, disliked, too similar to recent listens
     d. Re-rank 500 → top 30 using neural net (energy curve, variety, context)
  2. Write 600M × 30 = 18 billion (user_id, track_id) pairs to Cassandra
  3. Cache each user's playlist in Redis (7-day TTL)
  4. Monday 00:00 UTC → playlists go live for all users

Daily Mix Generation — Daily Batch

Daily Mixes are genre and mood clusters of the user's own taste:

User has listened to:
  - 200 indie rock songs → Daily Mix 1: Indie Rock
  - 150 jazz songs       → Daily Mix 2: Jazz
  - 80 hip-hop songs     → Daily Mix 3: Hip-Hop

Each mix: blend of saved songs user loves + ~30% new discoveries
Updated daily with fresh discoveries

Step 8 — Database Design

Database Selection

Data Database Reason
User profiles + accounts PostgreSQL ACID — subscription status must be strongly consistent
Track + album metadata PostgreSQL Relational — complex queries (artist → albums → tracks)
Playlists + tracks Cassandra Wide rows (playlist_id, position, track_id); very high volume
User listening history Cassandra Per-user time-series; append-only; very high write volume
Follows (user→artist) Cassandra Wide rows (user_id, artist_id); billions of edges
Search index Elasticsearch Full-text + audio features + NRT indexing
Recommendations cache Redis Pre-computed playlists; sub-ms reads
Now Playing state Redis Real-time per-user state; short TTL
Play counts Redis → PostgreSQL Counter — async flush to avoid hotspot write contention
Audio files S3 + CDN Object storage with global edge delivery

Track Metadata Table (PostgreSQL)

Table: tracks

  track_id        VARCHAR(22)   PRIMARY KEY   (Spotify base62 ID)
  title           VARCHAR(255)  NOT NULL
  artist_id       BIGINT        FK → artists
  album_id        BIGINT        FK → albums
  duration_ms     INT
  release_date    DATE
  genres          TEXT[]
  mood_tags       TEXT[]
  explicit        BOOLEAN       DEFAULT false
  play_count      BIGINT        DEFAULT 0     (eventually consistent)
  popularity      INT           (0–100, computed weekly)
  audio_features  JSONB         {tempo, energy, valence, danceability, ...}
  streaming_urls  JSONB         {"96k": "...", "160k": "...", "320k": "..."}
  waveform_url    TEXT
  status          VARCHAR       (PROCESSING | LIVE | TAKEDOWN)
  uploaded_at     TIMESTAMP

Playlist Table (Cassandra)

Table: playlists

Partition key:  playlist_id    VARCHAR
Clustering key: position       INT   (track order within playlist)

Columns:
  track_id       VARCHAR
  added_by       BIGINT        (user_id who added this track)
  added_at       TIMESTAMP

PRIMARY KEY (playlist_id, position)

Query: SELECT * FROM playlists WHERE playlist_id = ? ORDER BY position ASC

For collaborative playlists, added_by tracks who added each track.

User Listening History (Cassandra)

Table: listening_history

Partition key:  user_id        BIGINT
Clustering key: played_at      TIMESTAMP DESC

Columns:
  track_id       VARCHAR
  ms_played      INT           (how long listened before skip)
  context_type   VARCHAR       (playlist | album | artist | search | radio)
  context_id     VARCHAR       (id of the playlist/album that was playing)
  device_type    VARCHAR       (mobile | desktop | web | speaker)

PRIMARY KEY (user_id, played_at)

Used by the recommendation pipeline — what did you play, for how long, and in what context.

Follow Graph (Cassandra)

Table: user_follows_artist
  user_id      BIGINT
  artist_id    BIGINT
  followed_at  TIMESTAMP
  PRIMARY KEY (user_id, artist_id)

Table: artist_followers
  artist_id    BIGINT
  user_id      BIGINT
  followed_at  TIMESTAMP
  PRIMARY KEY (artist_id, user_id)

Two denormalized tables — one for "who does this user follow" (feed assembly), one for "who follows this artist" (fan-out on new release).


Step 9 — Caching Strategy

Redis Cache Map

Cache Key Pattern TTL Contents
Track metadata track:{track_id} 1 hr Full track object (title, artist, urls)
Discover Weekly dw:{user_id} 7 days 30 track IDs for this user's Discover Weekly
Daily Mix dm:{user_id}:{mix_num} 24 hrs Track IDs for each Daily Mix
Home screen rows home:{user_id} 30 min Pre-assembled home screen sections
Now Playing nowplaying:{user_id} 5 min Current track + position + device
Play count plays:{track_id} Eventual Counter — async flush to PostgreSQL
Search autocomplete suggest:{prefix} 1 hr Top 10 completions for typed prefix
Artist top tracks top:{artist_id} 1 hr Top 10 tracks by play count
User followed artists following:{user_id} 30 min List of followed artist IDs
Session + auth session:{user_id}:{device} 30 days JWT + refresh token

Play Count at Scale

Spotify tracks play counts for royalty calculations. At 80 million concurrent streams:

80M concurrent × average song length 3.5 min
= ~22M play events per minute
= ~370,000 play events per second

Incrementing a PostgreSQL counter 370K times/second would cause catastrophic lock contention.

Solution — Redis counter + async flush:

flowchart LR
    PlayEvent -->|INCR plays:{track_id}| RC[(Redis)]
    RC -->|every 30s| FLUSH[Batch Flush Worker]
    FLUSH -->|UPDATE play_count += delta| PG[(PostgreSQL)]
    FLUSH -->|async event| KF[Kafka → Royalty Pipeline]

Royalty calculations use the Kafka event stream — not the PostgreSQL counter directly.


Step 10 — Social and Real-Time Features

Now Playing

When a user plays a track, their profile shows "Currently playing: [song] by [artist]."

flowchart LR
    Client -->|track started| NP[Now Playing Service]
    NP -->|SETEX nowplaying:user_id 300| RC[(Redis\n5 min TTL)]
    FriendClient -->|GET /user/{id}/nowplaying| NP
    NP -->|GET nowplaying:{id}| RC
    RC --> FriendClient
  • Redis key expires after 5 minutes if no update (track ended or app closed)
  • Friends see "Listening to X" in real time via WebSocket or polling

Multi-Device Handoff (Spotify Connect)

Spotify Connect lets you start listening on your phone and hand off to a speaker or desktop seamlessly:

flowchart LR
    Phone -->|Start playing track X at position 1:23| NP[Now Playing Service]
    NP -->|SETEX device_state:{user_id}| RC[(Redis)]
    Speaker -->|WebSocket: new device joining| NP
    NP -->|GET device_state:{user_id}| RC
    RC -->|{track_id, position_ms: 83000}| NP
    NP -->|Play track X from 1:23| Speaker

State stored in Redis per user_id:

{
  "track_id":    "4u7EnebtmKWzUH",
  "position_ms": 83000,
  "is_playing":  true,
  "device":      "phone",
  "volume":      80
}

Any device joining the session reads this state and resumes from the exact position.

Collaborative Playlists

Multiple users can add tracks to the same playlist simultaneously:

  • Playlist stored in Cassandra — append-only adds are idempotent
  • Conflicts (two users adding at position 5 simultaneously) resolved by timestamp ordering
  • Real-time update pushed via WebSocket to all subscribers of that playlist

Step 11 — Offline Playback

How Offline Download Works

Premium users can download up to 10,000 tracks across 5 devices for offline listening.

flowchart LR
    Client -->|Download track| AG[API Gateway\nCheck Premium status]
    AG -->|Authorized| SS[Stream Service]
    SS -->|Encrypted download URL| Client
    Client -->|GET audio file + DRM key| CDN[CDN]
    CDN --> Client
    Client -->|Store encrypted locally| LocalDB[Encrypted local DB\nSQLite + AES-256]

DRM for Offline Files

Downloaded files are encrypted with a device-specific key:

  • The file cannot be played on another device — bound to device_id
  • License key expires after 30 days — user must reconnect to re-validate subscription
  • If subscription lapses, keys are not renewed and offline files become unplayable

Storage on Device

Device local storage:
  tracks/{track_id}/audio.ogg.enc    ← AES-256 encrypted audio
  tracks/{track_id}/metadata.json    ← title, artist, artwork URL
  license/{device_id}.key             ← device-bound decryption key
  playlists/{playlist_id}.json        ← offline playlist manifest

Step 12 — Scaling

Stream Scaling

flowchart TD
    80M_Listeners[80M Concurrent Listeners] --> CDN[CDN\n200+ Global PoPs]
    CDN -->|~90% cache hit| Listener
    CDN -->|10% cache miss| S3[Processed S3]
    S3 --> CDN
  • CDN absorbs 90% of traffic — S3 sees only 8M streams of origin traffic
  • Add CDN capacity (new PoPs) to scale — no backend changes required
  • Audio files are immutable — 1-year CDN TTL maximizes hit rate

API and Recommendation Scaling

flowchart LR
    LB[Load Balancer] --> SS1[Stream Service Pod]
    LB --> SS2[Stream Service Pod]
    LB --> SS3[Stream Service Pod]
    SS1 & SS2 & SS3 --> RC[(Redis Cluster\nCache)]
    RC -->|miss| PG[(PostgreSQL\nRead Replicas)]
  • All services are stateless — scale horizontally by adding pods
  • Redis Cluster shards cache keys across nodes
  • PostgreSQL read replicas absorb catalog reads

Recommendation Job Scaling

Weekly Discover Weekly batch:
  600M users × 30 tracks = 18 billion playlist entries

Apache Spark on AWS EMR:
  - Partition users into 10,000 batches of 60,000 users each
  - Each batch processed by one Spark executor
  - Full pipeline completes in ~4 hours (Sunday night)
  - Monday 00:00 UTC: playlists written to Cassandra + Redis

Database Sharding

Database Shard Key Notes
Tracks artist_id All tracks by one artist co-located
Playlists playlist_id Each playlist on one shard
History user_id All listening history for one user on one shard
Follows user_id All follow edges for one user on one shard

Step 13 — Failure Scenarios

Failure Impact Mitigation
CDN PoP outage Listeners in that region experience buffering CDN auto-routes to next nearest PoP; audio files also in S3
Redis (recs cache) down Home screen slow; Discover Weekly missing Redis Sentinel failover; fallback to Cassandra playlist store
Recommendation service down Home screen shows generic popular tracks Serve cached last-known recommendations; "Top Tracks" fallback
Elasticsearch down Search returns 503 Graceful degradation — show trending / recent searches
Kafka down Play events not recorded; encoding paused Events buffered locally; Kafka cluster RF=3; retry on reconnect
Encoder service crash Uploaded tracks stuck in PROCESSING Kafka redelivers on consumer restart; job retried automatically
S3 region outage Cache misses cannot be served S3 geo-replication; CDN serves from cache for active tracks
PostgreSQL primary down Track metadata writes fail Failover to replica; promote to primary; < 60s downtime
DRM license service down New offline downloads fail Existing downloads still play (cached key); online streams unaffected

Final Architecture

flowchart TD
    Client[Mobile / Desktop / Web\nClients] --> AG[API Gateway\nAuth + Rate Limiting]

    AG --> SS[Stream Service]
    AG --> SRCH[Search Service]
    AG --> REC[Recommendation Service]
    AG --> PLS[Playlist Service]
    AG --> USER[User Service]
    AG --> NP[Now Playing Service]

    SS --> CDN[CDN\n200+ Global PoPs]
    CDN -->|cache miss| S3A[Processed S3\nAudio Files]

    ART_UPLOAD[Artist Upload] --> S3R[Raw S3]
    S3R --> KF[Kafka Cluster]
    KF --> ENC[Encoder Workers\nFFmpeg]
    ENC --> S3A
    KF --> IDX[Search Indexer]
    KF --> RC_PIPE[Recommendation\nPipeline Spark]

    IDX --> ES[(Elasticsearch)]
    SRCH --> ES

    RC_PIPE --> FAISS[(FAISS\nEmbedding Store)]
    REC --> FAISS
    REC --> RDB[(Redis Cluster\nRec + Now Playing)]

    PLS --> PDB[(Cassandra\nPlaylists + History)]
    USER --> UDB[(PostgreSQL\nUsers + Tracks)]

    NP --> RDB
    NP --> WS[WebSocket\nConnected Devices]

Technology Stack

Layer Technology
API Gateway NGINX / Envoy + custom auth
Stream Service Java / Go
Encoder Service Python + FFmpeg on Kubernetes Jobs
Message queue Apache Kafka
Audio storage Amazon S3 (multi-region)
CDN Cloudflare / Akamai / Fastly
Track + user metadata PostgreSQL (primary + read replicas)
Playlists + history Apache Cassandra
Cache Redis Cluster
Search Elasticsearch (audio features + text)
Recommendations Apache Spark + TensorFlow + FAISS
Real-time sync WebSocket (Now Playing, Spotify Connect)
Audio codec OGG Vorbis (primary), AAC (Apple devices)
Offline DRM AES-256 device-bound encryption
Monitoring Prometheus + Grafana + ELK
Deployment Kubernetes (multi-region GCP + AWS)

Key Trade-Offs

Decision Option A Option B Choice and Reason
Audio codec MP3 (universal) OGG Vorbis OGG Vorbis — better quality per bit, royalty-free
CDN TTL for audio Short (1 day) Very long (1 year) 1 year — audio files are immutable; maximize CDN hit rate
Recommendation timing Real-time per request Batch pre-computed weekly/daily Batch — ML at 600M users cannot be real-time; weekly batch is correct
Play count storage PostgreSQL row-level increment Redis counter + async flush Redis counter — 370K events/sec would deadlock PostgreSQL
Search PostgreSQL full-text Elasticsearch Elasticsearch — audio features, fuzzy matching, NRT, autocomplete
Playlist storage PostgreSQL Cassandra Cassandra — append-only wide rows; 600M users × many playlists each
Streaming format Full file download HTTP range requests (chunked) Range requests — start in <250ms; no wasted bandwidth on skips
Offline protection No DRM (trust user) AES-256 device-bound DRM DRM — subscription enforcement; prevents leaking Premium content

Spotify vs YouTube vs Netflix

Dimension Spotify YouTube Netflix
Content type Audio (3–10 MB) Video (hundreds of MB) Video (GBs)
CDN TTL 1 year (fully immutable) 30 days (video segments) Proactive push nightly
CDN cache ratio ~90% of catalog cacheable Top 10% titles = 90% traffic Pre-positioned at ISP level
Key challenge Recommendation engine Upload pipeline + fan-out Delivery + resilience
Recommendation engine Best in class — Discover Weekly 70% from recommendations 80% from recommendations
Social features Follow, Now Playing, Collaborative Subscribe, Like, Comment Profiles only
Offline support Yes (device-bound DRM) Yes (YouTube Premium) Yes (Widevine DRM)
Real-time features Spotify Connect (multi-device sync) None Playback resume across devices

Common Interview Mistakes

  • ❌ Not mentioning OGG Vorbis — Spotify's codec choice is intentional and interview-relevant
  • ❌ Designing full file downloads — Spotify uses HTTP range requests to start playback in 250ms
  • ❌ Not explaining CDN TTL — audio files are immutable; 1-year TTL is correct and important
  • ❌ Play counts as direct DB increments — 370K events/sec requires Redis counters
  • ❌ No recommendation architecture — Discover Weekly is Spotify's most important feature
  • ❌ Using PostgreSQL for playlists — 600M users × many playlists each = Cassandra territory
  • ❌ Forgetting offline DRM — downloaded files must be subscription-enforced
  • ❌ No Spotify Connect design — multi-device handoff is a common interview question
  • ❌ Real-time recommendations — Discover Weekly is a weekly batch job, not real-time
  • ❌ No search design — music search needs audio features + full-text, not just SQL LIKE

Interview Questions

  1. How does Spotify stream audio so quickly? What is the role of HTTP range requests?
  2. Why did Spotify choose OGG Vorbis over MP3?
  3. How does Discover Weekly work? What three types of signals does it use?
  4. What is collaborative filtering and how does Spotify apply it?
  5. How does Spotify analyze audio content to recommend sonically similar tracks?
  6. How do you handle 370,000 play events per second without overloading the database?
  7. How does Spotify Connect (multi-device handoff) work?
  8. How is offline listening implemented? How is the subscription enforced offline?
  9. Why is Cassandra used for playlists instead of PostgreSQL?
  10. How does the CDN strategy for audio differ from video streaming?
  11. How would you generate Discover Weekly for 600 million users every Sunday night?
  12. How does Spotify's search handle audio features like "tempo" and "energy"?
  13. What happens if the recommendation service goes down?
  14. How do you prevent a hot track from overloading a single Cassandra partition?
  15. How would you design the "What's Popular in Your City" regional trending feature?

Summary

Concern Solution
Audio streaming HTTP range requests → CDN edge → start in 250ms; no full download
Audio encoding OGG Vorbis at 4 quality tiers (96 / 160 / 320 Kbps + HiFi) per track
CDN strategy 1-year TTL on immutable audio files; top 10M tracks fully cached at PoPs
Play count storage Redis INCR → async flush to PostgreSQL every 30 seconds
Track + artist metadata PostgreSQL — structured, ACID, complex joins
Playlists + history Cassandra — wide rows, append-only, very high volume
Search Elasticsearch — full-text + audio features + NRT indexing
Discover Weekly Weekly batch: Spark ANN retrieval (500 candidates) → neural net ranking → top 30
Daily Mixes Daily batch: cluster user's listening history by genre/mood → blend known + new
Now Playing Redis with 5-min TTL per user; pushed via WebSocket to friends and devices
Spotify Connect Redis device state per user; new device reads position → seamless handoff
Offline DRM AES-256 device-bound key; 30-day license TTL; unplayable if subscription lapses
Scale Stateless services + CDN + Redis + Cassandra + Spark batch

The core principle: Spotify is a recommendation and audio delivery problem. Audio is small and immutable — cache aggressively at CDN. The real engineering challenge is generating 600M personalized playlists every week. Discover Weekly is Spotify's most important system, not the streaming pipeline.