The 100 problems, solved / Video and media / 035 · Design Spotify

Design Spotify

Stream songs instantly, build playlists, and make shuffle feel random without repeats.

Concepts this design leans on

01The design, in the six-step framework

1. Requirements

Functional

  • Search the catalogue, play a track instantly, skip, seek, and continue on another device.
  • Playlists: create, reorder, follow; the same playlist on all of a user's devices.
  • Shuffle that feels random: no track repeats until the list is exhausted, and no artist twice in a row where possible.
  • Offline downloads for premium users.
  • Out of scope: podcasts, social features, recommendations beyond a simple radio.

Non-functional

  • 500 million monthly users, 100 million concurrent at peak; 100 million tracks.
  • Play starts in under 300 ms on a good connection, under 1 s on 3G.
  • Playlist edits visible on other devices within seconds; never lose an edit.
  • Availability of playback 99.99 percent; search may degrade to cached results.
  • Licensing: track availability varies by country and must be enforced at play time.
Back of the envelope

100 M tracks × 4 renditions × about 10 MB average = 4 PB of audio, small for object storage; the catalogue metadata is about 100 GB, which fits in memory on every search node.

100 M concurrent listeners × 160 kbps = 16 Tbps of audio egress at peak, almost entirely from the CDN.

Play starts: 100 M listeners starting a track every 3.5 minutes is about 500,000 play requests a second to the API, each needing a licence check and a manifest.

Playlists: 1 billion playlists averaging 100 tracks is 100 billion rows of 50 bytes, about 5 TB; edits are perhaps 50,000 a second worldwide.

2. Core entities

  • Track: track_id, artist_id, album_id, duration, renditions, availability by country.
  • Playlist: playlist_id, owner, name, ordered track list (with per-entry position keys), version.
  • Playback session: user_id, device_id, current track, position, queue, shuffle seed, updated_at.
  • User: user_id, country, plan, device list.
  • Play event: track_id, user_id, milliseconds played, timestamp (streamed for royalties).

3. API

  • GET /search?q=…: tracks, artists, albums; served from the in-memory catalogue index.
  • POST /play with track_id and device; returns a short-lived signed manifest URL after the licence check. Called by the player.
  • PUT /sessions/{device}: current track, position, queue; called by the player every few seconds and on pause.
  • PATCH /playlists/{id} with an operation (insert after position key, remove, move) and the client's last-seen version.
  • GET /playlists/{id}?since=version: fetch changes.

4. High-level design

ClientsEdgeServicesDataavailabilityplay eventsegmentsmissloadPlayer appCDN(audio segments)API gatewayPlayback service(licence, manifest)Playlist serviceSearch service(in-memory index)Audio objectsCatalogue DBPlaylist + session store(sharded by user)Play events(stream)

Pressing play:

  1. The app calls play with the track id; the playback service checks the user's country and plan against the track's availability (a cached catalogue record) and returns a signed manifest URL valid for a few minutes.
  2. The app fetches the manifest and the first audio segment from the nearest CDN edge; the first segment is small (about 2 s of audio) so the first byte arrives fast.
  3. While the first segment plays, the app prefetches the next segments and the first segment of the next track in the queue, so skips are instant.
  4. Every few seconds the app writes its session (track, position, queue) to the session store, sharded by user; another device can pick the session up and continue.
  5. Play events go to a stream for royalty accounting and for the radio feature's ranking signals.

5. Deep dives

How does play start in 300 ms when the audio is in object storage far away?

Fetching the whole track from origin before playing would take seconds. Audio is stored as small segments in object storage and served through the CDN; the first segment of every track in a user's playlists and recent queue is prefetched to the device when the app is idle, so the first 2 s are already local in most cases. The licence check is served from a cache of the catalogue's availability table, which changes a few times a day, so the API round trip is one cache read. The budget is 50 ms API, 100 ms first segment from the CDN, 50 ms decode and start; prefetch removes the middle term.

How do playlist edits merge when two devices reorder the same list offline?

OptionBehaviourTrade-off
Whole-list replace with last writer winsOne device's edits silently overwrite the other'sLoses work; unacceptable
Integer positions with renumberingInsert at position 3 shifts everythingTwo inserts conflict on every row after them
Fractional or string position keys per entryInsert between keys "m" and "n" creates "mm"; moves are delete plus insertConcurrent inserts at the same spot both survive in a deterministic order; keys need occasional rebalancing

Use per-entry position keys with operations sent as deltas tagged with the client's last-seen version; the server applies them in arrival order and both devices converge after a sync. This is the simple end of conflict resolution; it works because playlist edits are commutative inserts and removes rather than character edits.

How is shuffle random, without repeats, and consistent across devices?

Picking a random track each time repeats songs annoyingly often and makes the order different on each device. Instead the client generates a permutation of the playlist from a seed stored in the session: a Fisher-Yates shuffle, then a pass that spreads out tracks by the same artist so they are not adjacent. The seed and the current index are the only state, so another device reproduces the identical order, and no track repeats until the permutation is exhausted. A new seed is drawn when the user presses shuffle again. This is a client-side computation; the server only stores the seed.

What makes search answer across 100 million tracks in milliseconds?

The catalogue metadata is about 100 GB, so each search node holds a complete inverted index of titles, artists and albums in memory, rebuilt from the catalogue database a few times a day with a small delta feed for new releases. Queries never touch the database. Popularity is a ranking feature loaded from the play-event stream, so a trending song rises within an hour. Availability by country is a post-filter so that users do not see tracks they cannot play.

What does offline mean for the design?

Downloads are the same CDN segments, encrypted with a device-bound key and stored locally with a 30-day licence that renews whenever the app is online. Play events for offline listening are queued on the device and uploaded later with their original timestamps, which the royalty pipeline accepts within a window; this is the one place where late events are expected rather than exceptional.

6. What a strong answer sounds like

  • Point out that audio is small compared with video (4 PB total) and that the hard parts are start latency, playlist sync and licensing, not storage.
  • Quote 16 Tbps at peak from the CDN and 500,000 play starts a second to the API.
  • Explain prefetching the first segment as the reason play starts in under 300 ms.
  • Expect follow-ups on shuffle (seeded permutation, artist spreading), on cross-device handoff, and on royalty-grade event accounting with late offline uploads.
  • A common mistake is to store playlists as a single JSON blob per playlist, which makes concurrent edits a last-writer-wins disaster.

02Where this question was reported

  • IGotAnOffer · SWE list
  • recapio.com · YouTube mock 'Google system design interview: Design Spotify (with ex-Google EM)' by IGotAnOffer Engineering; EM Mark; original watch URL not resolved.