System design sample solution

System design: design a music streaming app

A step-by-step system design answer you could actually give in 60 minutes.

System design interview · Sample 60-minute answer · Not Amazon's actual design · Independent, not affiliated with Amazon

The system design question

Design a music streaming service like Spotify. Users can search songs, artists and albums, make playlists and like songs. Playback should start almost immediately for millions of listeners at once, and the service should basically never be down.

Agree on the scope (5 minutes)

Say up front what you're covering.

We'll designWe'll skip
Playing songsRecommendations
Searching songs, artists and albumsPaying artists
Playlists and liked songsPodcasts and video
Staying up when things breakUploading music from labels

Then agree on what "good" means:

  • Starts fast: a song starts playing within about 200 ms of pressing play.
  • Always up: playback keeps working even if a database or a whole region is having a bad day.
  • Search is quick: results show up as you type, in under about 100 ms.
  • Your stuff is safe: a playlist edit or a like is never lost once you've seen it saved.

Rough numbers (5 minutes)

These are assumptions: 100 million listeners a day, each listening about an hour, songs about 3.5 minutes long, 100 million songs in the catalog, and each song stored at three quality levels, about 15 MB in all.

~1.5 PB
of audio files
~4M
people listening at once, on average
~650 Gbps
of audio going out, on average
~20K
songs starting per second
QuestionRough mathWhat it means
How much audio?100 million songs × about 15 MBAbout 1.5 PB. It goes in object storage, never in a database
How many listening at once?100 million × 1 hour ÷ 24 hoursAbout 4 million, maybe 10 million at the evening peak
How much bandwidth?4 million × 160 kbpsAbout 650 Gbps on average, well over 1 Tbps at peak. Only a CDN can handle that
How many song starts?100 million × ~17 songs an hour ÷ 86,400About 20K a second. Each one is a small request to us

The overall design (10 minutes)

Listener appsearches and playsAPI servicessearch, playlists, likesCatalog and user datadatabasesPlayback servicehands out audio linksCDNstreams the audio
Small requests go to our services. The audio itself comes straight from a CDN close to the listener.
  1. Every song is stored once in object storage, at a few quality levels, cut into small chunks of a few seconds each.
  2. When you press play, the app asks the playback service for the song. It checks you're allowed to play it and returns short-lived links to the chunks on a CDN.
  3. The app fetches chunks from the nearest CDN server. Popular songs are already sitting there, so most plays never reach our storage.
  4. Search goes to a search index built from the catalog. Playlists and likes go to their own databases, split by user or playlist.
  5. Every play is sent to an event stream, which feeds play counts, charts and anything else that can happen later.

What the apps need to do

ActionWho does itWhat's sent
SearchAppWhat you've typed so far, plus a filter like songs, artists or albums
Get a song, album or artistAppIts ID. Returns names, cover art links and the track list
Start playbackAppSong ID and your connection quality. Returns links to the audio chunks
Create or edit a playlistAppPlaylist ID, the change, and a request ID so retries don't apply twice
Like or unlike a songAppSong ID
Get my libraryAppA cursor and how many items
Report playsAppA batch of song IDs and how long each was played

What we store and where

DataWhere it goesWhy
Audio chunksObject storage behind a CDNHuge, never changes, read from everywhere
Songs, albums and artistsRelational database, read through a cacheStructured and linked, and it changes slowly
Search indexSearch engine, copied to every regionBuilt from the catalog for matching as you type
PlaylistsKey-value database, split by playlist IDA playlist is read and written as one thing
Likes and libraryKey-value database, split by user IDAll of one person's library sits together
Play eventsEvent stream, then cheap long-term storageCounts and charts are worked out later

Step 1: Starting playback almost instantly (12 minutes)

Our servers should never send audio. The playback service only answers 'can this person play this song, and where are the chunks?', which is a small, fast lookup. The bytes come from a CDN, which is just a big cache spread around the world.

Songs are cut into chunks of a few seconds, at a few quality levels. To start fast, the app asks for the first chunk at a lower quality, starts playing as soon as it lands, then switches up once it knows how fast the connection is. You hear sound after one small download, not a whole file.

Popularity is very lopsided. A small set of songs gets most of the plays, so those are almost always already on the CDN server near you. Old or rare songs miss the CDN and come from storage, which is slower. A middle layer of cache between the CDN and storage stops those misses from all hitting storage at once.

The app can also hide the wait. While a song plays, it fetches the start of the next song in the playlist or album, so skipping feels instant. It keeps recently played songs on the phone too.

Keeping links private

The chunk links are signed and expire after a few minutes. Anyone can fetch from the CDN, but only with a link we handed out. That stops people sharing a permanent link to a song.

Step 2: Search for songs, artists and albums (8 minutes)

We don't search the catalog database. It's built for 'get song 123', not 'everything that starts with "tay"'. So we keep a separate search index built from the catalog, with entries for song, artist and album names.

The catalog only changes when new music is added, which is mostly a weekly batch from labels. Each change goes on a queue and indexers update the index from it. Being a few minutes behind is fine.

Search runs as you type, so matching has to work on the start of words and forgive small typos. Ranking matters more than matching: lots of things match 'love', so sort by how often each result gets played, which we already count from play events.

The index for 100 million songs is a few hundred GB, so it's split into shards, with copies in every region. Popular searches repeat a lot, so cache the results of the most common ones for a minute or so.

Step 3: Playlists, likes and staying up (10 minutes)

Likes are a long list per person, read all at once when you open your library. A key-value database split by user ID fits that well, and it's the usual relational vs non-relational call. Splitting by user is plain sharding, and everyone's library is roughly the same size.

Playlists are split by playlist ID, since a popular playlist is read by millions of people who aren't its owner. Those hot ones get cached. Every edit carries a request ID, so if the app retries after a timeout, the same song isn't added twice. That's idempotency.

Shared playlists can get two edits at once. Give each playlist a version number, and reject an edit made against an old version. The app reloads and tries again. Edits are rare, so that almost never happens.

What if a whole region goes down?

Run in several regions. The catalog and search index barely change, so every region has a full copy. Playlists and likes are copied between regions within a second or so. If a region fails, its users move to the next nearest one, and at worst a like from the last second shows up late. Choosing to stay up over being perfectly in sync is a consistency trade we're happy to make here.

Choices you should be able to defend

We choseInstead ofBecauseWe'd reconsider if
Audio from a CDN, our servers only hand out linksStreaming audio through our own serversWe'd need over a Tbps of our own bandwidthNever at this scale
Small chunks at a few quality levelsOne file per songPlayback starts after one small download and adjusts to the connectionSongs were tiny and connections always fast
Separate search indexSearching the catalog databaseMatching as you type needs a different kind of indexThe catalog was small enough for a database's built-in search
Playlists and likes copied between regions a second lateOne main region for all writesAny region can take writes, so we stay up when one failsEdits needed to be exact everywhere, like a bank balance

What could go wrong (5 minutes)

What happensWhat we do about it
A CDN has trouble in one areaThe app switches to a second CDN for that area
Object storage is slowPopular songs still play from the CDN. Only rare songs are affected
The playlist database is downShow the copy the app saved last time, read-only, and queue edits until it's back
The search index is downSearch fails, but playback and libraries still work. Show recent searches from the app
A huge album drops at midnightPush its first chunks to the CDN ahead of time, so the rush hits a warm cache

Follow-up questions (5 minutes)

How would you support offline listening?

Let the app download the chunks for a playlist and store them encrypted, with a license that has to be renewed every few weeks.

How do you count plays for charts and paying artists?

Read the play events from the stream, drop duplicates by event ID, and add them up in batches. Nobody needs it to the second.

How would 'listening with friends' work?

Keep a live connection per listener and have one session hold the current song and position. Each app plays from the CDN on its own and syncs to that position.

How the 60 minutes add up

MinutesWhat you're doing
0–5Agree on what you're building
5–10Rough numbers
10–20Draw the overall design
20–32Starting playback almost instantly
32–40Search for songs, artists and albums
40–50Playlists, likes and staying up
50–55What could go wrong
55–60Follow-up questions

Key concepts used here

Further reading

We wrote this sample answer ourselves.

Practice Loop is independent and isn't affiliated with or endorsed by Amazon.