?

Side quest 07Audio streaming

Spotify.

The World's Most Personalized Music Experience at Scale

INTERMEDIATE24 MIN READ6 PHASES · 40 MIN INTERVIEW+120 XP

Mission brief

Design a music streaming platform

Quest log0/7 stages · 0/5 checkpoints · 0/7 quick checks
monthly active users (Q2 2026)
777M
tracks in the catalogue
100M+
from tap to first audio (design target)
< 200 ms
streams starting per second at peak (our estimate)
~175K

Company names and logos are trademarks of their owners, shown only to identify each company. This course isn't affiliated with or endorsed by any of them.

watch it first · newThe whole design in a 17-minute animated lecture4 quiz checkpoints · ask the AI about any moment · captions, chapters and speed
Optional · timed run · 6 phases · 40 min

Want it harder? Hide the model answer and work each phase in a scratchpad against its time budget, then reveal and score yourself. Or simply read on: the checkpoints below work either way.

Intro · 2 min

How Do You Make 777 Million People Feel Like You Built an App Just for Them?

Imagine this challenge: You need to serve 100+ million songs to 777 million users across 184 markets. Every user expects instant playback—less than 200ms from tap to music. Every user expects recommendations that feel eerily personal. And you need to do this with an engineering team that pioneered the "squad" model of autonomous teams.

This is Spotify—and it's not just a music app. It's a masterclass in personalization at scale, microservices architecture, and building developer platforms that thousands of engineers can use without stepping on each other's toes.

Spotify playback · one signed URL, audio from the CDN edge (origin only on a miss), play events sent async
LIVESpotify playback · one signed URL, audio from the CDN edge (origin only on a miss), play events sent async
The Spotify scale (mid-2026)
Users
  • Monthly Active Users: 777 Million (Q2 2026)
  • Premium Subscribers: 300 Million (Q2 2026)
  • Conversion Rate: ~39%
Content
  • Songs: 100+ Million
  • Podcasts: ~8 Million titles
  • Audiobooks: 800,000+ (select regions)
  • Playlists: 8+ Billion (user-created, 2024)
Engagement
  • Average listening time: ~114 minutes/day (third-party estimate; Spotify doesn't publish one)
  • Discover Weekly: 100B+ tracks streamed since its 2015 launch
  • New artist discoveries from personalized recommendations: ~1 in 3
Infrastructure
  • Markets: 184
  • Employees: ~7,300
  • Microservices: 2,000+ backend services
  • Daily events collected: 1+ Trillion (2024)
Business
  • 2025 Revenue: €17.2 Billion
  • Market Share: 31.4% of global music subscribers (end of 2025, MIDiA)
  • Royalties paid (2025): $11 Billion+ to rights holders

This is the system we'll design today—and discover how Spotify built the world's most personalized audio experience.

ExplainerThe whole quest: Spotify

Spotify serves 777 million monthly users from 100 million+ tracks, starting playback in under 200 ms: 5 billion streams a day, ~175,000 a second at peak, ~5.5 Tbps. The Stream Service authorizes once and returns a signed URL, audio comes from the CDN edge (origin only on a miss) and play events go asynchronously to Pub/Sub. The deep dives cover hybrid recommendations and cold start, device, edge and origin caching, the event pipeline and breakers, then album drops, licensing and tunnels, and exactly-once play counts for royalties.

The Interview Begins#

You walk into the interview room. The interviewer smiles and gestures to the whiteboard.

Interviewer

"Thanks for coming in. Today we're going to design a music streaming service like Spotify. I'm interested in how you think about scale, personalization, and delivering a seamless listening experience. Please think out loud—this is collaborative."

They write on the whiteboard:

Design a Music Streaming Platform
Requirements
  • Stream audio to hundreds of millions of users globally
  • Near-instant playback (< 200ms to first audio)
  • Highly personalized recommendations
  • Search across 100M+ tracks
  • Support both free (ad-supported) and premium tiers
  • Handle massive catalog: songs, podcasts, audiobooks
Interviewer

"Take a few minutes to think about this, then walk me through your approach. We have about 45 minutes."

Phase 1 · Stage 1 of 7 · 5 min budget · 1 min read

Phase 1: Requirements Clarification (5 minutes)#

Before diving in, you take a breath and start asking questions.

Your Questions#

Transcript
You

"Before I start designing, I'd like to clarify a few requirements. First, what's our target scale—how many concurrent users should we support?"

Interviewer

"At peak, we might have 50 million concurrent listeners. Average is around 20-30 million."

You

"For playback latency, what's acceptable? I want to understand the user experience bar."

Interviewer

"Users tap a song and expect music within 200 milliseconds. No buffering during playback except on very poor connections."

You

"How personalized are the recommendations? Are we talking basic 'similar artists' or deeply personal like Discover Weekly?"

Interviewer

"Deeply personal. We want users to feel the app knows their taste better than they do. Recommendations should work even for new users with minimal listening history."

You

"What about offline playback? Do premium users expect to download music?"

Interviewer

"Yes, premium users can download. That's a key differentiator from the free tier."

You

"Perfect. Let me summarize the requirements as I understand them."

Functional Requirements#

Functional requirements
1. Audio playback
  • Stream songs, podcasts, and audiobooks
  • Support multiple quality levels (96kbps to 320kbps)
  • Adaptive bitrate based on network conditions
  • Offline download for premium users
  • Gapless playback between tracks
2. Discovery & search
  • Full-text search across songs, artists, albums, podcasts
  • Typo tolerance and autocomplete
  • Browse by genre, mood, activity
  • Personalized recommendations
3. User features
  • Create and manage playlists
  • Follow artists and friends
  • Like/save songs to library
  • View listening history
4. Monetization
  • Free tier with ads
  • Premium tier (ad-free, offline, higher quality)
  • Family and student plans

Non-Functional Requirements#

Non-functional requirements
1. Scale
  • 50 million concurrent users (peak)
  • 100+ million songs in catalog
  • 500+ billion events/day for analytics
  • 5+ billion streams/day
2. Latency
  • Playback start: < 200ms
  • Search results: < 100ms
  • API responses: < 50ms (p99)
3. Availability
  • 99.99% uptime for playback
  • Graceful degradation (recommendations can fail, playback must not)
4. Data
  • Strong consistency for user data (playlists, library)
  • Eventual consistency acceptable for recommendations

Phase 2 · Stage 2 of 7 · 5 min budget · 2 min read

Phase 2: Back of the Envelope Estimation (5 minutes)#

You

"Let me work through the numbers to understand the scale."

Your turn · checkpoint 1not answered

Before the numbers: how heavy is audio?

250 million people listen each day, about 20 songs each, and a song at 160 kbps is about 4 MB. How many streams start per second on average and at a 3× peak? How much egress is that, and how does it compare with video?

Think it through, or jot it down. Then compare.

One strong answer

  • Streams: 250M × 20 = 5 billion a day, ≈ 58,000 per second on average and ~175,000 at peak.
  • Data: 5B × 4 MB = 20 PB a day, about 1.85 Tbps on average and ~5.5 Tbps at peak.

That's roughly a tenth of a video service's egress. Bandwidth is manageable with an ordinary CDN; the hard problems are the 200 ms start and personalisation for hundreds of millions of people.

Traffic Estimation#

WorksheetStreaming traffic
  • Base numbers
    • Daily Active Users: 250 million
    • Songs per user per day: 20 songs (average)
    • Average song length: 3.5 minutes
    • Average file size (160 kbps): ~4 MB per song
  • Daily calculations
    • Total streams/day: 250M × 20 = 5 billion streams
    • Streams per second: 5B ÷ 86,400 = ~58,000 streams/sec
    • Peak (3x average): ~175,000 streams/sec
  • Bandwidth
    • Data per stream: ~4 MB
    • Daily data transfer: 5B × 4 MB = 20 PB/day
    • Average bandwidth: 20 PB × 8 ÷ 86,400 s ≈ 1.85 Tbps
    • Peak bandwidth (3x): ~5.5 Tbps

Storage Estimation#

WorksheetAudio storage
  • Song catalog
    • Total songs: 100 million
    • Average song (all quality levels): ~25 MB (multiple encodings)
    • Total audio storage: 100M × 25 MB = 2.5 PB
  • User data
    • Users: 777 million
    • Per user (playlists, library): ~50 KB average
    • Total user data: ~39 TB
  • Event data (analytics)
    • Events per day: 500 billion
    • Daily event storage: ~250 TB/day
    • Yearly (compressed): ~10 PB

Key Metrics Summary#

Estimation summary
Traffic
  • Peak streams: 175,000 /second
  • Daily streams: 5 billion
  • API requests: ~500,000 /second (peak)
Storage
  • Audio catalog: 2.5 PB
  • User data: 39 TB
  • Event data: ~10 PB/year
Bandwidth
  • Peak egress: ~5.5 Tbps
  • Daily transfer: 20 PB
Quick check3 questions · +5 XP each

Requirements and estimation

  1. Q1/3 250M daily active users each play about 20 songs a day, and peak is 3x the average. Roughly what peak stream rate should you design for?

    Answer & explanation

    Answer: B

    250M x 20 = 5B streams/day, and 5B / 86,400 is about 58,000/sec on average. Tripling that for peak gives ~175,000/sec; ~58,000 is only the average.

  2. Q2/3 Which data must be strongly consistent, and which can be eventually consistent?

    Answer & explanation

    Answer: D

    A song you just added to a playlist should show up on your other device, so user data needs strong consistency. A Discover Weekly list that's a few hours behind your latest plays hurts no one.

  3. Q3/3 Which storage item keeps growing and soon outweighs the whole audio catalog?

    Answer & explanation

    Answer: A

    The catalog is about 2.5 PB and grows slowly, and user data is tiny. The analytics events add roughly 10 PB every year, so the event pipeline drives long-term storage and processing cost.

Phase 3 · Stage 3 of 7 · 10 min budget · 2 min read

Phase 3: High-Level Design (10 minutes)#

You

"Now let me sketch out the high-level architecture."

Your turn · checkpoint 2not answered

Sketch it first: what sits on the play path?

A listener taps a song. What must happen before the first note plays, and what can safely happen afterwards?

Think it through, or jot it down. Then compare.

One strong answer

  • Before the first note: check the session and entitlement (tier, licensing in this country), pick the encoding, issue a signed URL, and fetch audio from the CDN edge. Often the client already has the track cached or prefetched, so even that fetch is skipped.
  • After: the play event goes asynchronously to the event pipeline, which feeds listening history, royalty counts and recommendations.
  • Never on the play path: recommendations. The requirement says it directly: recommendations can fail, playback must not.

System Architecture#

DiagramSpotify high-level architecture
%% title: Spotify high-level architecture
flowchart TD
  subgraph CL["Clients"]
    direction TB
    IOS@{ icon: "tech:apple", label: "iOS" }
    AND@{ icon: "tech:android", label: "Android" }
    DSK@{ icon: "sd:desktop", label: "Desktop" }
    WEB@{ icon: "sd:browser", label: "Web" }
  end
  subgraph GCP["Google Cloud Platform"]
    direction TB
    CDN@{ icon: "sd:cdn", label: "CDN (e.g. Akamai/Fastly)<br/>Audio delivery, static assets, caching" }
    APIG["API Gateway<br/>Rate limiting, auth, routing, load balancing"]:::edge
    subgraph MS["Microservices layer"]
      direction TB
      US["User Service"]:::core
      CS["Catalog Service"]:::core
      SS["Search Service"]:::core
      ST["Stream Service"]:::hot
      PL["Playlist Service"]:::core
      RC["Reco Service"]:::core
      SO["Social Service"]:::core
      AD["Ad Service"]:::core
      US ~~~ PL
      CS ~~~ RC
      SS ~~~ SO
      ST ~~~ AD
    end
    subgraph DL["Data layer"]
      direction TB
      PG@{ icon: "tech:postgresql", label: "Postgres (Users)" }
      CA@{ icon: "tech:cassandra", label: "Cassandra (Events)" }
      RD@{ icon: "tech:redis", label: "Redis (Cache)" }
      BQ@{ icon: "tech:bigquery", label: "BigQuery (Analytics)" }
      GCS@{ icon: "tech:gcs", label: "GCS (Audio)" }
      EL@{ icon: "tech:elasticsearch", label: "Elastic (Search)" }
      BT@{ icon: "tech:bigtable", label: "Bigtable (Reco)" }
      PG ~~~ GCS
      CA ~~~ EL
      RD ~~~ BT
    end
    subgraph ES["Event streaming"]
      direction TB
      PS@{ icon: "tech:pubsub", label: "Pub/Sub (Events)" }
      DF@{ icon: "tech:dataflow", label: "Dataflow (Process)" }
      MLP["ML Pipeline"]:::service
    end
    APIG --> MS
    MS ~~~ DL
    DL ~~~ ES
  end
  CL ==> CDN
  CL --> APIG
  class IOS,AND,DSK,WEB client
  class CDN,RD cache
  class PG,CA,BQ,GCS,EL,BT store
  class PS queue
  class DF service

Data Flow: Playing a Song#

DiagramPlayback flow — playing a song
%% title: Playback flow — playing a song
sequenceDiagram
  autonumber
  participant C as Client
  participant G as API Gateway
  participant S as Stream Service
  participant CDN as CDN edge
  participant O as GCS origin
  participant P as Pub/Sub
  C->>G: TAP TO PLAY
  G->>S: AUTHORIZATION
  Note over S: Checks user subscription tier<br/>Regional availability · Content licensing
  S-->>C: GENERATE SIGNED URL · token, expires in 1 hour
  Note over C,S: https://cdn.spotify.com/audio/abc123?token=xyz&expires=...
  C->>CDN: STREAM FROM CDN
  alt cached (99%+ hit rate for popular)
    CDN-->>C: audio · adaptive bitrate based on network
  else cache miss
    CDN->>O: fall back to GCS origin
    O-->>CDN: audio
    CDN-->>C: audio · adaptive bitrate based on network
  end
  C-)P: TRACK EVENTS · play/skip events
  Note over P: → Analytics pipeline → Recommendations
  Note over C,P: TOTAL LATENCY: < 200ms to first audio
Quick check3 questions · +5 XP each

High-level design

  1. Q1/3 Why does the Stream Service return a signed, expiring URL instead of the CDN asking the backend about every request?

    Answer & explanation

    Answer: C

    Authorization happens once, at play time. After that the CDN can verify the signature itself, which keeps the backend off the hot path for every audio byte, and the one-hour expiry keeps leaked links from staying useful.

  2. Q2/3 A user plays an obscure track that isn't cached at the edge. What happens?

    Answer & explanation

    Answer: A

    On a cache miss the CDN goes back to the GCS origin. Popular tracks hit the cache over 99% of the time, while long-tail tracks sometimes pay the origin round trip.

  3. Q3/3 The client sends play and skip events to Pub/Sub asynchronously. Why not record them synchronously before playback starts?

    Answer & explanation

    Answer: D

    Tracking feeds analytics and recommendations, but the music doesn't need it to start. Sending events asynchronously protects the latency budget for what the user is waiting on.

Phase 4 · Stage 4 of 7 · 20 min budget · 11 min read

Phase 4: Deep Dives (20 minutes)#

Interviewer

"Let's dive deeper. Tell me about the recommendation system."

Your turn · checkpoint 3not answered

Cold start, twice

Collaborative filtering learns from what people play. What do you recommend to a brand-new user, and how does a song released this morning, played by nobody yet, ever get recommended?

Think it through, or jot it down. Then compare.

One strong answer

  • New users: onboarding picks (a few favourite artists), locale and demographic signals, editorial and popular playlists, then learn fast from the first handful of plays and skips.
  • New songs: content-based signals. Audio analysis turns the track into a feature vector (tempo, energy, mood) so it can be matched to listeners of similar-sounding music; add metadata, the artist's existing fans and editorial placement.
  • Then hand over: once real plays accumulate, collaborative filtering takes over. Keeping a share of recommendations for exploration is what lets new music collect those plays.

Deep Dive 1: Recommendation System#

The Problem#

Personalization challenge
Scale
  • 777 million users with unique tastes
  • 100+ million songs to choose from
  • Generate personalized playlists for each user
Cold start problem
  • New users: No listening history
  • New songs: No user interactions
Exploration vs exploitation
  • Recommend what users will like (exploitation)
  • Introduce new music (exploration)

The Solution: Hybrid Recommendation Architecture#

DiagramThree recommendation models
%% title: Three recommendation models
flowchart TD
  CF["COLLABORATIVE FILTERING<br/>'Users like you also liked...'<br/>Matrix factorization"]:::service
  CB["CONTENT BASED<br/>'Songs with similar tags'<br/>NLP on blogs, reviews, titles"]:::service
  AA["AUDIO ANALYSIS<br/>'Songs that sound similar'<br/>CNN on audio spectrograms"]:::service
  EN["ENSEMBLE MODEL<br/>Combines all signals with learned<br/>weights from A/B testing"]:::core
  CF & CB & AA ==> EN

Collaborative Filtering#

Matrix factorization
User-Song Matrix (sparse)
Song1 Song2 Song3 ... Song100M
User1 5 0 3 ... 2
User2 0 4 0 ... 0
User3 3 0 5 ... 4
...
Decompose into
  • User vectors: 777M × 128 dimensions
  • Song vectors: 100M × 128 dimensions

Similarity = dot_product(user_vector, song_vector)

For Discover Weekly
  1. Find users with similar taste profiles
  2. Find songs those users love that you haven't heard
  3. Rank by predicted engagement

Audio Analysis (CNN)#

Convolutional neural networks

Input: Audio spectrogram

Process

Raw audio → Mel spectrogram → CNN → 128-dim feature vector

Output features
  • Tempo, key, mode
  • Danceability, energy, valence
  • Acousticness, instrumentalness
Use case: Cold start for new songs
  • Works even with zero user interaction data
  • Powers "song radio" feature

Implementation#

pythonUserProfile · 121 lines
# recommendations/discover_weekly.py

"""
Discover Weekly Generation

Hybrid recommendation combining collaborative filtering,
content-based filtering, and audio analysis.
"""

from dataclasses import dataclass
from typing import List, Dict, Set
import numpy as np


@dataclass
class UserProfile:
    user_id: str
    embedding: np.ndarray      # 128-dim taste vector
    top_artists: List[str]
    top_genres: List[str]
    listening_history: Set[str]


class DiscoverWeeklyGenerator:
    """
    Generates personalized weekly playlists.
    
    Applies concepts:
    - Week 5: Eventual consistency for recommendations
    - Week 8: Batch processing pipeline
    """
    
    def __init__(self, user_store, song_store, similar_users_index):
        self.user_store = user_store
        self.song_store = song_store
        self.similar_users_index = similar_users_index
        
        # Weights from A/B testing
        self.weights = {
            'collaborative': 0.4,
            'content': 0.3,
            'audio': 0.2,
            'popularity': 0.1,
        }
        
        self.playlist_size = 30
        self.exploration_ratio = 0.2
    
    async def generate_playlist(self, user_id: str) -> List[str]:
        """Generate Discover Weekly for a user."""
        user = await self.user_store.get_profile(user_id)
        
        # Get candidates from collaborative filtering
        collab_candidates = await self._get_collaborative_candidates(user)
        
        # Get candidates from content similarity
        content_candidates = await self._get_content_candidates(user)
        
        # Merge and score
        all_candidates = self._merge_candidates(
            collab_candidates, content_candidates
        )
        scored = await self._score_candidates(user, all_candidates)
        
        # Filter already-heard songs
        filtered = [
            (song_id, score) for song_id, score in scored
            if song_id not in user.listening_history
        ]
        
        # Exploitation: top songs
        exploit_count = int(self.playlist_size * (1 - self.exploration_ratio))
        exploit_songs = [s for s, _ in filtered[:exploit_count]]
        
        # Exploration: outside comfort zone
        explore_songs = await self._get_exploration_tracks(
            user, 
            count=self.playlist_size - exploit_count,
            exclude=set(exploit_songs)
        )
        
        return self._shuffle_with_variety(exploit_songs + explore_songs)
    
    async def _get_collaborative_candidates(
        self, user: UserProfile
    ) -> Dict[str, float]:
        """Find songs from similar users."""
        similar_users = await self.similar_users_index.find_similar(
            user.embedding, k=1000
        )
        
        candidates = {}
        for similar_user_id, similarity in similar_users:
            loved_songs = await self.user_store.get_loved_songs(
                similar_user_id, limit=100
            )
            for song_id, engagement in loved_songs:
                score = similarity * engagement
                candidates[song_id] = candidates.get(song_id, 0) + score
        
        return candidates
    
    async def _score_candidates(
        self, user: UserProfile, candidates: Dict[str, float]
    ) -> List[tuple]:
        """Score using ensemble of all signals."""
        scored = []
        
        for song_id, base_score in candidates.items():
            song = await self.song_store.get_song(song_id)
            
            # Combine signals
            final_score = (
                self.weights['collaborative'] * base_score +
                self.weights['content'] * self._content_similarity(user, song) +
                self.weights['audio'] * self._audio_similarity(user, song) +
                self.weights['popularity'] * song.popularity_score
            )
            scored.append((song_id, final_score))
        
        return sorted(scored, key=lambda x: x[1], reverse=True)

Deep Dive 2: Audio Streaming#

Interviewer

"How do you achieve < 200ms playback latency?"

Your turn · checkpoint 4not answered

Under 200 milliseconds from tap to music

List the places the audio for a tap can come from, fastest first, and the tricks that make most taps feel instant.

Think it through, or jot it down. Then compare.

One strong answer

  • The device: recently played tracks, offline downloads, and the next tracks in the queue, prefetched while the current one plays. For most taps nothing goes over the network.
  • The CDN edge: a small share of the catalogue accounts for most plays, so popular tracks are essentially always at the edge, with access tokens validated there and no backend round trip.
  • The origin: only on an edge miss, for the long tail.
  • Start light, then upgrade: begin with a lower bitrate or a small first chunk so audio starts at once, then switch up.

Multi-Layer Caching#

Audio delivery architecture
  1. Layer 1: Client cache
    • Recently played songs
    • Prefetched next songs
    • Offline downloads (Premium)
    • 1-10 GB local storage
  2. Layer 2: CDN edge (e.g. Akamai/Fastly)
    • Edge locations worldwide
    • Top 20% of songs always cached (80% of plays)
    • 99%+ cache hit rate for popular content
    • Token validation at edge
  3. Layer 3: Origin (GCS)
    • All 100M+ songs
    • Multiple quality encodings per song
    • Multi-region replication
ExplainerPlaying a song: signed URL, edge cache, origin, prefetch

The Stream Service checks tier, region and licensing once and returns a URL signed for 1 hour; the CDN edge verifies the token itself and serves popular tracks from cache (99%+ hit rate) in under 200 ms. A long-tail track missing at the edge is fetched from the GCS origin, so its first play is slower, and a prefetched next track plays straight from the client cache with no network wait.

Audio Quality Tiers#

Spotify audio quality
Tier Bitrate Codec Use Case
Low ~24 kbps Not published Extreme data saving
Normal ~96 kbps Ogg Vorbis Standard / mobile data
High ~160 kbps Ogg Vorbis Free tier max
Very High ~320 kbps Ogg Vorbis Premium only
Lossless up to 24-bit/44.1 kHz (~1,000+ kbps) FLAC Premium (rolled out Sept–Oct 2025)

Spotify's support page gives the bitrates as approximate and names FLAC for Lossless; its web player streams AAC instead (128 kbps Free, 256 kbps Premium). The Ogg Vorbis column reflects the format Spotify's apps have long used, not a per-tier list Spotify publishes.

Why Ogg Vorbis?
  • Open source (no licensing fees)
  • Better quality than MP3 at same bitrate

Implementation#

pythonPlaybackResponse · 82 lines
# streaming/playback_service.py

"""
Playback Authorization and URL Signing

Optimized for < 30ms authorization latency.
"""

from dataclasses import dataclass
from typing import List
from datetime import datetime, timedelta, timezone
import hmac
import hashlib
import base64


@dataclass
class PlaybackResponse:
    stream_url: str
    expires_at: datetime
    quality: str
    prefetch_urls: List[str]


class PlaybackService:
    """
    Handles playback authorization.
    
    Applies concepts:
    - Week 4: Multi-tier caching
    - Week 2: Timeout management
    """
    
    def __init__(self, rights_service, catalog_service, cdn_config):
        self.rights = rights_service
        self.catalog = catalog_service
        self.cdn_base = cdn_config['base_url']
        self.signing_key = cdn_config['signing_key']
    
    async def get_playback_url(
        self, user_id: str, song_id: str, quality: str
    ) -> PlaybackResponse:
        """Generate signed streaming URL."""
        import asyncio
        
        # Parallel checks for speed
        rights_ok, song = await asyncio.gather(
            self.rights.check(user_id, song_id),
            self.catalog.get_song(song_id)
        )
        
        if not rights_ok:
            raise PlaybackNotAllowedError()
        
        # Generate signed URL
        file_id = f"{song_id}_{quality}.ogg"
        expires = datetime.now(timezone.utc) + timedelta(hours=1)
        url = self._sign_url(file_id, expires)
        
        # Prefetch hints for next songs
        prefetch = await self._get_prefetch_urls(user_id, song_id, quality)
        
        return PlaybackResponse(
            stream_url=url,
            expires_at=expires,
            quality=quality,
            prefetch_urls=prefetch
        )
    
    def _sign_url(self, file_id: str, expires: datetime) -> str:
        """HMAC-signed CDN URL."""
        expires_ts = int(expires.timestamp())
        message = f"/audio/{file_id}:{expires_ts}"
        
        sig = hmac.new(
            self.signing_key.encode(),
            message.encode(),
            hashlib.sha256
        ).digest()
        sig_b64 = base64.urlsafe_b64encode(sig).decode()
        
        return f"{self.cdn_base}/audio/{file_id}?expires={expires_ts}&sig={sig_b64}"
Quick check3 questions · +5 XP each

Recommendations and audio delivery

  1. Q1/3 A song released this morning has zero plays. Which model can still place it in recommendations?

    Answer & explanation

    Answer: B

    Collaborative filtering needs interactions the song doesn't have yet, and its popularity is zero. Audio features like tempo, energy and danceability are computed from the audio itself, which is how new songs get around cold start.

  2. Q2/3 Discover Weekly has 30 tracks and an exploration ratio of 0.2. How many tracks deliberately come from outside the user's comfort zone?

    Answer & explanation

    Answer: C

    The generator takes 30 x (1 - 0.2) = 24 top-scored "exploit" tracks, and the remaining 6 are exploration picks. The exploration share keeps the playlist from turning into a filter bubble.

  3. Q3/3 How do prefetched next songs help a user who skips to the next track?

    Answer & explanation

    Answer: A

    The client cache is the fastest layer. When the playback response includes prefetch URLs, the next track's audio can be on the device before the user taps, which makes near-instant skips and gapless playback possible.

Deep Dive 3: Microservices & Backstage#

Interviewer

"How do you manage 2,000+ microservices?"

Backstage Developer Portal#

Backstage platform
Software catalog

Every service registered with:

  • Owner (which squad)
  • Description and documentation
  • API specifications
  • Dependencies
  • Health metrics
Core features
  • Software Templates (Scaffolder) — New service wizard
  • TechDocs — Docs as code
  • Search — Find any service

100+ plugins: Kubernetes, GitHub, PagerDuty, Datadog...

Result: Spotify reports much faster engineer onboarding

Squad Model#

Spotify organizational model

Squad (a small, Scrum-sized team)

  • Cross-functional: Engineers, Designer, Product Owner
  • Owns: A feature or set of services end-to-end
  • Autonomy: Decides how to work (Scrum, Kanban, etc.)

Example: "Search Squad" owns search experience

Tribe (kept under ~100 people)

  • Collection of squads in related area
  • Sized under the Dunbar number, "smaller than 100 people or so" (Kniberg & Ivarsson, 2012)

Example: "Music Discovery Tribe" includes Search, Browse, Radio

Chapter

  • Specialists across squads within a tribe
  • Led by Chapter Lead (career growth, standards)

Example: All backend engineers in Music Discovery

Guild

  • Community of interest across company
  • Voluntary, knowledge sharing

Example: "Web Guild" - all web developers

Service Communication#

pythonCircuitBreakerConfig · 69 lines
# infrastructure/circuit_breaker.py

"""
Circuit Breaker for Service Calls

Protects against cascading failures.
"""

from dataclasses import dataclass
from datetime import datetime
from typing import Callable, Optional, Any


@dataclass
class CircuitBreakerConfig:
    failure_threshold: int = 5
    success_threshold: int = 3
    timeout_seconds: float = 30.0


class CircuitBreaker:
    """
    States: CLOSED → OPEN → HALF_OPEN → CLOSED
    
    Applies concepts:
    - Week 2: Circuit breaker pattern
    - Week 2: Graceful degradation
    """
    
    def __init__(self, name: str, config: CircuitBreakerConfig):
        self.name = name
        self.config = config
        self.state = "CLOSED"
        self.failure_count = 0
        self.last_failure: Optional[datetime] = None
    
    async def call(
        self, func: Callable, *args,
        fallback: Optional[Callable] = None, **kwargs
    ) -> Any:
        """Execute with circuit breaker protection."""
        if self.state == "OPEN":
            if self._should_try_reset():
                self.state = "HALF_OPEN"
            elif fallback:
                return await fallback(*args, **kwargs)
            else:
                raise CircuitOpenError(self.name)
        
        try:
            result = await func(*args, **kwargs)
            self._on_success()
            return result
        except Exception:
            self._on_failure()
            if fallback:
                return await fallback(*args, **kwargs)
            raise
    
    def _on_success(self):
        if self.state == "HALF_OPEN":
            self.state = "CLOSED"
        self.failure_count = 0
    
    def _on_failure(self):
        self.failure_count += 1
        self.last_failure = datetime.utcnow()
        if self.failure_count >= self.config.failure_threshold:
            self.state = "OPEN"

Deep Dive 4: Event Processing#

Interviewer

"How do you process 500 billion events per day?"

Event Pipeline#

DiagramSpotify event pipeline
%% title: Spotify event pipeline
flowchart TD
  SRC["SOURCES<br/>Mobile/Desktop apps (plays, skips, searches)<br/>Backend services (errors, latency)<br/>External (ad impressions)"]:::client
  PS@{ icon: "tech:pubsub", label: "Google Cloud Pub/Sub<br/>1 trillion messages/day capacity<br/>Partitioned by event type<br/>At-least-once delivery" }
  SP["STREAMING PATH (Dataflow)<br/>Real-time: Trending now · Live stats"]:::hot
  BP@{ icon: "tech:dataproc", label: "BATCH PATH (Dataproc)<br/>Daily/weekly: ML training · Reports" }
  subgraph STO["Storage"]
    direction TB
    BT@{ icon: "tech:bigtable", label: "Bigtable (features)" }
    BQ@{ icon: "tech:bigquery", label: "BigQuery (analytics)" }
    GCS@{ icon: "tech:gcs", label: "GCS (archive)" }
  end
  SRC ==> PS
  PS ==> SP
  PS --> BP
  SP ==> STO
  BP --> STO
  class PS queue
  class BP service
  class BT,BQ,GCS store

Scale: 500B events/day, ~250 TB/day uncompressed (~500 B/event), 10M+ BigQuery queries/month

Implementation#

pythonStreamEndEvent · 77 lines
# data/event_processor.py

"""
Event Processing Pipeline

Processes user events for recommendations and analytics.
"""

from dataclasses import dataclass
from datetime import datetime, timedelta
from typing import Optional
import json


@dataclass
class StreamEndEvent:
    user_id: str
    content_id: str
    total_played_ms: int
    track_duration_ms: int  # full length of the track
    end_reason: str  # "completed", "skipped", "error"
    timestamp: datetime


class EventProcessor:
    """
    Processes events from Pub/Sub.
    
    Applies concepts:
    - Week 3: Stream processing
    - Week 8: Late-arriving data
    """
    
    def __init__(self, pubsub, bigtable, bigquery):
        self.pubsub = pubsub
        self.bigtable = bigtable
        self.bigquery = bigquery
        self.allowed_lateness = timedelta(hours=24)
    
    async def process(self, raw: bytes) -> None:
        event = self._parse(raw)
        if not event:
            return
        
        # Drop very late events
        if datetime.utcnow() - event.timestamp > self.allowed_lateness:
            return
        
        # Always write to BigQuery
        await self.bigquery.insert('events.streams', event)
        
        # Calculate engagement for recommendations
        if isinstance(event, StreamEndEvent):
            engagement = self._calculate_engagement(event)
            await self.pubsub.publish('taste-updates', {
                'user_id': event.user_id,
                'content_id': event.content_id,
                'engagement': engagement
            })
    
    def _calculate_engagement(self, event: StreamEndEvent) -> float:
        """
        Engagement scoring:
        - Completed: 1.0
        - Skipped early: -0.5
        - Partial: proportional
        """
        if event.end_reason == 'error':
            return 0.0
        if event.end_reason == 'skipped' and event.total_played_ms < 30000:
            return -0.5
        
        if event.track_duration_ms <= 0:
            return 0.0
        
        completion = event.total_played_ms / event.track_duration_ms
        return min(1.0, completion)
Quick check3 questions · +5 XP each

Microservices and event processing

  1. Q1/3 The recommendation service starts failing repeatedly. What should a user who taps play experience?

    Answer & explanation

    Answer: D

    The requirement is that recommendations can fail but playback must not. Once the breaker opens, calls go straight to the fallback, so a failing dependency neither blocks nor slows the core feature.

  2. Q2/3 Where should "Trending now" be computed?

    Answer & explanation

    Answer: B

    Trending needs to reflect what's happening now, so it belongs in the streaming path. The batch path suits daily or weekly work like model training and reports, where completeness matters more than latency.

  3. Q3/3 A user skips a track after 12 seconds. What does the event processor publish as the engagement signal?

    Answer & explanation

    Answer: C

    A skip under 30 seconds scores -0.5 and is published to taste-updates, so recommendations learn what the user dislikes. Only events more than 24 hours late are dropped.

Phase 5 · Stage 5 of 7 · 1 min

Phase 5: Scaling and Edge Cases#

Scaling for Major Releases#

Handling Taylor Swift album drop
  • Pre-event (1 week before) —
    Pre-cache album on ALL CDN nodes · Scale API servers 2-3x · Pre-compute recommendation updates · War room ready
  • Release moment —
    Feature flags for instant rollout · CDN serves from cache · Rate limiting protects backends · Async processing for non-critical
  • Post-release (first hour) —
    Monitor error rates · Auto-scale on demand · Graceful degradation if needed

Edge Cases#

Edge cases
1. Cold start (new users)

Solution: Onboarding questions, demographic signals, editorial playlists, rapid learning from first 10 songs

2. Cold start (new songs)

Solution: Content-based + audio analysis, artist fan base, editorial placement

3. Regional licensing

Solution: Rights check at authorization, search excludes unavailable, VPN detection

4. Network transitions

Solution: 20+ second buffer, prefetch next songs, offline cache, graceful quality degradation

Quick check3 questions · +5 XP each

Scaling and edge cases

  1. Q1/3 Why pre-cache a huge album on all CDN nodes a week before release?

    Answer & explanation

    Answer: A

    A synchronized release creates a predictable spike. Warming every edge ahead of time means the CDN serves from cache at the release moment, while rate limiting and extra API capacity protect the backend.

  2. Q2/3 A user in a country where a track isn't licensed taps a shared link to it. Where is playback stopped?

    Answer & explanation

    Answer: C

    The rights check runs before any signed URL exists, so no URL means no stream. Search also excludes unavailable tracks, but a shared link skips search, which is why the check has to live in authorization.

  3. Q3/3 A brand-new user has no listening history. Why can't collaborative filtering personalize their first session, and what fills the gap?

    Answer & explanation

    Answer: B

    Collaborative filtering needs a taste vector learned from plays. New users start with explicit signals and curated content, and the system then learns quickly from their first ~10 songs.

Phase 6 · Stage 6 of 7 · 1 min

Phase 6: Monitoring#

Spotify monitoring dashboard

Illustrative targets and readings for this design, not numbers Spotify publishes.

Playback health
Metric Target Current
Time to first audio (p50) < 150ms 142ms
Time to first audio (p99) < 500ms 380ms
Playback success rate > 99.9% 99.94%
Rebuffer rate < 0.1% 0.03%
CDN health
Metric Target Current
Cache hit rate > 95% 96.2%
Edge latency (p99) < 50ms 28ms
API health
Metric Target Current
Request rate ~500K/sec 487K
Error rate < 0.1% 0.04%
Latency (p99) < 100ms 67ms
Data pipeline
Metric Target Current
Events ingested/sec ~6M 5.8M
Processing lag < 5 min 2.1 min
Alerts
  • CRITICAL: Playback < 99%, API errors > 1%
  • WARNING: Cache hit < 90%, Pub/Sub backlog > 10M
Quick check3 questions · +5 XP each

Playback health

  1. Q1/3 CDN cache hit rate drops from 96% to 88%. Which playback metric should move first?

    Answer & explanation

    Answer: B

    Every miss adds an origin fetch to that play's start time. The slow tail shows it first; success rate only drops if the origin starts failing too.

  2. Q2/3 Event pipeline processing lag grows from 2 minutes to 40. What do listeners notice right now?

    Answer & explanation

    Answer: A

    Play events leave the client asynchronously and nothing on the play path waits for them. A backlog is a freshness problem for downstream features, not an outage.

  3. Q3/3 Why set targets on p99 time to first audio, not just p50?

    Answer & explanation

    Answer: B

    At hundreds of thousands of plays a second, the slowest 1% is still thousands of people every second. The tail is where the complaints, and usually the real problems, are.

Final challenge · Stage 7 of 7 · 2 min

Final Challenge: Count Every Play Exactly Once#

The interviewer turns the marker around. "Last one, and this time it's money."

Interviewer

"Artists are paid from play counts. Your event pipeline is at-least-once, phones play songs offline and upload the events hours later, and some accounts are bots streaming on loop. How do you produce play counts accurate enough to pay royalties on?"

The royalty brief
  • Events can arrive twice: clients retry, the pipeline redelivers
  • Offline plays arrive hours late, sometimes after the day has ended
  • Some accounts stream the same tracks around the clock
  • Payout reports must be final and auditable
Your turn · checkpoint 5not answered

From at-least-once events to exactly-once counts

Where does each problem get solved: duplicates, late plays and fake plays? What does a single play event need to carry for this to work?

Think it through, or jot it down. Then compare.

One strong answer

  • A unique play ID: the client creates it when playback starts, and every retry or redelivery carries the same ID. The pipeline deduplicates on it, so at-least-once delivery becomes exactly-once counting.
  • Event time, not arrival time: each event records when the music played. Offline plays are counted into the period they were played in, accepted as late data within an allowed-lateness window before a period's report is finalised.
  • A rule for what counts: a minimum listening time before a stream counts (for example, 30 seconds), so skips aren't paid.
  • Fraud before payout: per-account and per-device anomaly detection (loops around the clock, many accounts on one device) excludes suspicious plays before reports are finalised.
  • Auditable: keep the raw deduplicated events immutable and reconcile reports against them, so any payout can be explained.
Quick check4 questions · +5 XP each

Final challenge · Quest check

  1. Q1/4 Events are delivered at-least-once. What turns that into exactly-once play counts?

    Answer & explanation

    Answer: B

    Delivery guarantees stop at the pipeline; correctness comes from an idempotent consumer. The same ID on every copy lets the counter keep exactly one.

  2. Q2/4 A phone plays 30 songs offline on a flight and uploads the events six hours later. How should they be counted?

    Answer & explanation

    Answer: C

    Royalties follow when the music was played. Windows keyed on event time, with allowed lateness and a finalisation cutoff, handle late data without double counting.

  3. Q3/4 Why is (user ID, track ID, upload time) a bad deduplication key?

    Answer & explanation

    Answer: A

    A key that includes the time of sending is new on every retry, the same mistake the payment side quests warn about for idempotency keys. The key has to be fixed when the play happens.

  4. Q4/4 5 billion streams a day is about how many per second on average?

    Answer & explanation

    Answer: B

    5,000,000,000 ÷ 86,400 ≈ 57,900 per second on average, and about three times that at peak.

Wrap-up · 2 min

Interview Conclusion#

Transcript
Interviewer

"Excellent work. You've demonstrated strong understanding of streaming systems, personalization, and microservices architecture. Any questions?"

You

"Thank you! How does Spotify prevent filter bubbles in recommendations?"

Interviewer

"Great question. Spotify hasn't published its exact mix, but in your design the 20% exploration ratio is the key lever, and Spotify's own features like 'Enhance' add variety on top."

Summary: Concepts Applied#

Concepts from 10-week course in Spotify design
  • Week 1: Data at scale
    • Partitioning — User data by user_id, events by timestamp
    • Replication — Multi-region audio and user data
    • Hot keys — Popular songs cached at all CDN edges
  • Week 2: Failure-first design
    • Circuit breakers — Protect recommendation service
    • Timeouts — 30ms budget for authorization
    • Graceful degradation — Popular content fallback
  • Week 3: Messaging & async
    • Pub/Sub — 500B+ events/day
    • Async processing — Recommendation updates
  • Week 4: Caching
    • Multi-tier — Client → CDN → Origin
    • Hot content — Always cached, never evicted
  • Week 5: Consistency
    • Eventual — Recommendations, play counts
    • Strong — Playlists, user library
  • Week 8: Analytics pipeline
    • Stream — Dataflow for real-time
    • Batch — Dataproc for ML training
  • Week 10: Production readiness
    • SLOs — < 200ms playback, 99.99% availability
    • Observability — Comprehensive metrics
Why Spotify is an engineering marvel
Scale
  • 777 million users across 184 markets
  • 1+ trillion events collected daily (2024)
  • Near-instant starts: a 265 ms median was already reported back in 2010
Personalization
  • ~1 in 3 new artist discoveries via personalized recommendations
  • Discover Weekly: 100B+ tracks streamed since 2015
  • Three-model hybrid approach
Engineering culture
  • Squad model influenced entire industry
  • Backstage: Open-sourced developer portal
  • 2,000+ microservices, autonomous teams

Sources#

Official:

Statistics:

Architecture:

Organization:

Self-Assessment Checklist#

After studying this case study, you should be able to:

After studying this case study, you should be able to0/11 ready
  • Design multi-tier caching for media delivery
  • Explain collaborative filtering and matrix factorization
  • Design hybrid recommendation systems
  • Handle cold start for users and content
  • Architect event pipelines at 500B+ events/day
  • Implement circuit breakers and graceful degradation
  • Design URL signing for CDN authentication
  • Explain streaming vs batch processing trade-offs
  • Design for predictable and unpredictable traffic spikes
  • Articulate the squad model for team organization
  • Design developer portals for microservices

This case study covers Spotify's architecture for delivering personalized audio experiences at massive scale.

this lesson in shorts · 3

play them all →

One idea from this lesson in about a minute each, made for your phone.

End of Side quest 07 · Spotify
Clear the questFinish Spotify.Stages clear as you read them. Marking complete unlocks after ~5 more min of reading.
  • 0/7stages cleared

Follow-up drills

0 of 11 practised

You presented Spotify. Now the interviewer pushes back. Answer each follow-up out loud or in the box, then reveal a strong answer.

Follow-up 1 / 11 · scale & numbers

“Your estimate puts peak egress at 5.5 Tbps. Is bandwidth the hard part of this design?”

Pushes on · knowing which number actually drives the design

Revealing marks it practised.
design sheet · 7 diagrams · pdfRevise the whole design in ten minutesThe numbers, architecture, sequence, class and state diagrams, decisions and failure modes of this quest on a few printable pages.open the sheet →

Go deeper · reading trail

all reading →

Real write-ups from the teams that built these systems. Each opens on the publisher's site in a new tab.

SpotifyBlog post · ~9 min

Spotify's Event Delivery – The Road to the Cloud (Part I)

Igor Maravić · 2016
What
The first post in Spotify's series on rebuilding the pipeline that collects client events.
Key idea
Every play and search becomes an event; the old Kafka and Hadoop pipeline is explained, and why Spotify chose Google Cloud managed services for the new one.
Why here
The event processing deep dive of this quest: play events feed royalties and recommendations, so delivery must be complete.
taught here: Event processing
SpotifyBlog post · ~12 min

Personalization at Spotify using Cassandra

Kinshuk Mishra, Matt Brown · 2015
What
How Spotify's personalisation stack served recommendations for 60 million users.
Key idea
Kafka collects logs, Storm processes them in real time and Crunch runs batch jobs on Hadoop, and Cassandra serves the resulting user profiles at low latency.
Why here
The hybrid recommendation architecture of this quest: batch and streaming models meeting in one serving store.
taught here: Recommendation system
SpotifyBlog post · ~6 min

What the Heck is Backstage Anyway?

2020
What
Spotify explains Backstage, its developer portal, the day after open-sourcing it.
Key idea
A single portal catalogues every service, its owners and docs, with plugins for tooling, so thousands of engineers can find and run things in one place.
Why here
The 'Microservices & Backstage' deep dive of this quest, from the team that built it.
taught here: Backstage developer portal

New lessons by email

New lessons, side quests and case files as they ship, at most once a week. No spam, unsubscribe anytime.

Discussion · …

Public: everyone reading this lesson sees comments. Be kind and keep it on the material.

Sign in to ask a question or answer one. Reading stays open to everyone.
loading the discussion…