InterviewLegend

Design Music Recommendations

Signature problem
Alex Chen45 min readUpdated
In this lesson
  • Frame Discover Weekly as a candidate-generation funnel that ends in a bandit-built playlist
  • Build Spotify's stack: three models, Voyager ANN retrieval, and BaRT ranking toward the 30-second stream
  • Explain the audio model (cold start), approximate retrieval, and why the ranker explores

Understanding the problem

The prompt

Every Monday, hand each of roughly 600 million listeners a personalized playlist of 30 tracks they have never played but will probably love. Design the system behind Discover Weekly.

Discover Weekly is the surface Spotify is most associated with, and it is the natural prompt for this round because it exercises every layer of the recommendation stack at once. The output is small and exact: an ordered list of 30 tracks per listener, refreshed weekly, made of songs the listener has not heard. The work is everything that produces those 30 tracks out of a catalog of more than 100 million.

Notice what the problem is not. There is no social graph, no posting, no fan-out of one person's action to their followers' inboxes. A track is an item in a huge corpus, and a listener is a point in the same space as the tracks. So this is item retrieval driven by a user, not a write-fan-out feed. If you walked in ready to draw a follower table and an inbox, that machinery has no role here. Say so in the first minute.

The Spotify angle
Discover Weekly is not one model, it is three model families voting tracks into a single vector space, and then a bandit deciding what to show. Collaborative filtering learns from co-listening: who plays and saves a track alongside what else. An audio model listens to the raw waveform so a brand-new song can be recommended before a single person has played it. An NLP model reads the text around music, playlist titles, blogs, reviews, to find tracks people describe the same way. Each family produces an embedding, all three land in one shared f-dimensional space, and a track near you in that space is a candidate. The reward that links all three families is concrete: a recommendation counts as right when the listener streams the track past 30 seconds. Open by naming the three families and the 30-second reward. That is the shape of Spotify's real stack, not a generic recommender.

Functional requirements

Keep the surface small. For a given listener, generate a pool of candidate tracks from their taste, rank that pool, and assemble an ordered playlist of fresh tracks they have not heard. It should stay personal, surface new and cold tracks rather than only the obvious hits, and not repeat across weeks. Everything else is a thread the interviewer can pull.

In scope

  • Build a taste vector per listener from their play / skip / save history and retrieve nearby candidate tracks from the catalog
  • Cover cold tracks: a song uploaded this week, with no listening history, should still be reachable
  • Rank the candidate pool toward a real engagement reward (a stream past 30 seconds), not a raw click
  • Assemble the final 30 as an ordered, diverse sequence, filtering tracks the listener has already heard
  • Refresh weekly per listener, and serve the closely related Home page shelves online between refreshes

Below the line (name it, then skip)

  • The audio storage and CDN that stream the track once it is picked (a separate playback design)
  • The play / skip / save event pipeline itself (Spotify's Pub/Sub and Scio/Dataflow backbone; we consume its output, we do not design it here)
  • Training infrastructure for the three models beyond the offline step the serving path depends on
  • Search and the podcast NL-retrieval stack (its own design; this is recommendation, not query-driven retrieval)

Non-functional requirements

Reason each one against a target. The generation cadence splits in two, and that split drives most of the design choices. Discover Weekly itself is a weekly batch: 600 million playlists produced over a refresh window, so per-listener latency does not matter, throughput does. The Home page is the opposite, an online surface where the same models are read live and the shelf has to render in tens of milliseconds. Scale is the catalog: more than 100 million tracks, so retrieval has to prune to a few hundred candidates before any expensive ranking runs. Freshness matters because a track uploaded this week has to be reachable, which is exactly what a pure co-listening model cannot do. Quality is the product itself: tracks that feel discovered, not obvious, with enough variety that the 30 are not all the same artist.

One number reframes the design. The pool of tracks plausibly related to an active listener can run into the tens of thousands of co-listened songs, and Discover Weekly shows exactly 30. So this is a funnel with a brutal narrowing ratio, and the engineering is doing the wide narrowing cheaply and the final ordering carefully. Generate broad and dumb, rank and sequence narrow and smart.

  • Listeners: ~675M MAU. About 600M get a weekly Discover Weekly playlist. Generation is a weekly batch, so throughput is the constraint, not per-request latency.
  • Catalog: 100M+ tracks. Every track needs an embedding, and a large fraction has thin or zero listening history, which is what forces a content model alongside collaborative filtering.
  • Reward: Stream > 30s. The canonical "got it right" event. The ranker optimizes for a 30-second-plus stream, not a click, because a click before a skip is a near-worthless signal.
  • Output: 30 tracks, ordered. A small, exact, ordered list per listener, fresh (unheard) and de-duplicated across weeks. The funnel collapses tens of thousands of candidates to 30.

The set-up

Core entities

Name the objects before drawing. Four carry the design, and the key idea is that the first two live in the same space.

1

Listener

A user, represented for recommendation as a taste vector in the shared embedding space. The vector is built from their play / skip / save history; it is the retrieval key for Discover Weekly. Note the listener is a point in the same f-dimensional space as the tracks, which is what makes "find tracks near this listener" a single nearest-neighbor lookup.

2

Track

A song, also a vector in that same space. More than 100M of them. A track gets its vector from collaborative filtering if it has listening history, from the audio model if it is cold, and from the NLP model via the text around it. Every track is a candidate; the design comes down to mapping a listener to nearby tracks.

3

Interaction

A logged play / skip / save / playlist-add event. These are the implicit-feedback signals that train collaborative filtering: a saved track is positive, a track skipped in five seconds is negative. They arrive through Spotify's event pipeline and are the raw material for both training and the 30-second reward.

4

Recommendation

A candidate track proposed for a listener, carrying its source (which model retrieved it), its retrieval distance, and ranking features. The Discover Weekly playlist is the top 30 recommendations after ranking, sequencing, and the unheard filter.

The split that matters is retrieval versus ranking. Retrieval reads the embedding index to answer "which tracks are even plausibly for this listener," and it runs against the whole catalog, so it must be cheap per candidate. Ranking reads rich features on a few hundred survivors to decide order and which 30 make the cut, and it can afford a heavier model because the set is small. Every component below lands on one side of that line.

API

The external surface is thin. The weekly job writes a playlist; the Home page reads recommendations live. The interesting structure is internal: a listener's vector fans into the index, candidates merge, then a ranker and a sequencer collapse them into the ordered playlist.

The interface
RequestFire-and-forget
Discover Weekly batchrecommendation servicegenerate the weekly playlist for a listener
generateWeekly(userId, count=30) → ordered [trackId]
the 30 unheard, ranked, sequenced tracks for this listener
recommendation serviceANN index (Voyager)retrieve nearest tracks to the listener taste vector
query(userVector, k=500) → [trackId, distance]
the few hundred nearest candidate tracks across all model families
approximate, not exact; recall traded for a sublinear probe over 100M+ vectors
recommendation serviceBaRT rankerrank the merged candidate pool toward the 30-second-stream reward
rank(userContext, candidates[]) → scored, ordered list
candidates by predicted stream probability, with explore/exploit applied
Home pagerecommendation servicerender the live Home shelves online (BaRT)
GET /home?userId={id} → [shelf{title, cards[], reason}]
ordered shelves and cards with explanation text, in tens of ms
clientinteraction loglog plays, skips, saves with dwell time for training and the reward
logEvent(userId, trackId, action, msPlayed)
msPlayed is the load-bearing field: the 30-second reward and skip-negatives both read it

The one field worth stating aloud is msPlayed on the interaction log. It looks minor next to the action type. It is the input to the entire reward story. A play with msPlayed under a few seconds is a skip, a negative label. A play past 30 seconds is the success event the ranker is trained to produce. Log how long they listened, not just that they pressed play.

High-level design

Build the smallest funnel that captures "generate broad, rank and sequence narrow," then go deep on the pieces that are specific to Spotify. A listener's history feeds three model families that each emit embeddings. All embeddings live in one shared space and load into an ANN index. At generation time the listener's taste vector probes the index for a few hundred nearby tracks. Those candidates merge and drop anything already heard, BaRT ranks them toward the 30-second reward, and a sequencer orders and diversifies the survivors into the final 30. Those stages are the pipeline end to end; the depth in this round is in how the three generators disagree about what "similar" means and why the ranker is a bandit.

Read the colors as roles. Blue marks the listener and the rendered Discover Weekly playlist, the two ends. Violet marks the three model families that produce embeddings. Green marks the shared embedding space and the Voyager ANN index they write into. Amber marks retrieval, BaRT ranking, and the sequencer, the compute that turns a taste vector into 30 ordered tracks.

Loading diagram...

1) Build the vectors offline. Before any listener is served, three model families turn the catalog and the interaction log into embeddings. Collaborative filtering factors the co-listening matrix into a vector per listener and per track. The audio CNN reads each track's spectrogram and emits a content vector, including for the tracks the matrix has never seen. The NLP model embeds the text around each track. All of this is heavy batch compute that runs on a cadence, the visible one being the weekly Discover Weekly refresh, so none of it is on any request path. The output is one shared f-dimensional space holding 100M+ track vectors plus a vector per active listener. That split, expensive training offline and cheap lookup online, is what makes a 100M-track recommender servable at all.

2) Retrieve nearby tracks. At generation time the listener's taste vector probes the Voyager ANN index for its nearest few hundred tracks. This is a single approximate-nearest-neighbor query, sublinear in the 100M-track catalog because the index is an HNSW graph rather than a flat scan. The probe returns far more than the final 30, on the order of a few hundred to a few thousand. The ranker downstream needs a pool to choose from, and the unheard filter will drop a chunk. Candidates from all three model families come back mixed, since they share one space, each tagged with its source and its distance. The slowest part of generation is not this lookup; it is the offline training behind it. Retrieval itself is fast, which is the entire point of paying the training cost up front.

3) Rank and sequence. The candidate pool merges, de-duplicates, and drops every track the listener has already heard. BaRT then scores what survives toward the 30-second-stream reward, balancing tracks it is confident about against uncertain tracks it wants data on. The top survivors pass to the sequencer. It caps per-artist repetition, spreads the set across facets of the listener's taste, and orders the final 30 so the playlist has a shape instead of a flat relevance gradient. For Discover Weekly this writes a playlist; for the Home page the same scoring runs live and BaRT additionally picks shelf order and the explanation text. The expensive ranking only ever sees a few hundred candidates that retrieval already vouched for, which is what lets it afford a heavier per-candidate model.

Size the vectors and the load

Two numbers decide the shape of the serving tier. The embedding index is the first. Take 100M tracks at a modest 128-dimensional fp32 vector: that is 128 times 4, about 512 bytes per track, so roughly 51GB of raw vectors. That fits in memory on a beefy host or a small shard set. It is part of why Spotify can serve recommendations without heavy ML in real time. The expensive model already ran. What is left is a vector probe over a few tens of gigabytes. Push the dimension to 256 or add listener vectors and you cross 100GB, at which point the index shards across a fleet and the vectors get quantized below fp32 to claw back memory. Either way this is a small index by web standards, and the HNSW graph on top is what keeps a probe sublinear.

The second number is the weekly batch. About 600M listeners each get 30 tracks, so the job materializes on the order of 18 billion track slots over its refresh window. That never touches a live request, so the constraint is throughput, not latency: spread it across the window and across a cluster and it is a large but ordinary batch. The online side is the Home page, where the same models are read live. Hundreds of millions of daily listeners open the app several times a day. Home rendering lands in the tens of thousands of queries per second at steady state, higher at peak, and each request has a tens-of-milliseconds budget. So the system runs at two completely different tempos: a giant weekly batch for Discover Weekly and a low-latency online path for Home, both reading the same embedding index.

  • Embedding index: ~51 GB. 100M tracks at a 128-d fp32 vector, ~512 bytes each. Small enough to hold in memory; push past ~256-d and it shards across a fleet with quantized vectors. The HNSW graph is what keeps each probe sublinear.
  • Weekly batch: ~18B slots. 600M listeners times 30 tracks, materialized over the weekly refresh window. Off the request path entirely, so the constraint is throughput across a cluster, not per-listener latency.
  • Online load: Tens of k QPS. The Home page reads the same models live, ~tens of thousands of queries per second steady and higher at peak, each with a tens-of-ms budget. Two tempos, one index.
  • Funnel ratio: Tens of thousands to 30. An active listener has tens of thousands of plausibly-related tracks. Retrieval prunes to a few hundred, ranking and sequencing emit 30. Cheap at the wide end, careful at the narrow end.

Technology choices

Every box maps to a real piece of Spotify's stack. The round lives in the trade-off follow-up, so pair each with why that class of tool fits and where it strains.

  • Collaborative filtering is matrix factorization on implicit feedback, specifically logistic matrix factorization. You factor the interaction matrix rather than store it because of size. A 600M-by-100M listener-track matrix is almost entirely empty and far too large to keep or compare directly. Compress it into a few hundred latent dimensions and similarity becomes a cheap dot product. Implicit feedback matters because Spotify has no star ratings. It has plays, skips, and saves. The model treats a save as a confident positive and a fast skip as a negative, weighting each by how strong the signal is. Be ready for the cold-start probe. A track with no plays has no row in the matrix and so no vector here. The audio model fills that gap.
  • The audio model is a convolutional neural network over spectrograms. A CNN is the right tool because a spectrogram is essentially an image, time on one axis, frequency on the other. Convolutions are built to find local patterns in images, here the rhythmic and tonal patterns that make two songs sound alike. The payoff is that it depends only on the waveform, so it places a brand-new upload in the embedding space immediately, no listening history required. The follow-up is "isn't audio similarity worse than co-listening," and the honest answer is yes for tracks that have plays. That is why this model is a fallback and a coverage layer rather than the primary signal: it earns its keep entirely on cold and long-tail tracks.
  • Retrieval runs on an approximate-nearest-neighbor index (Spotify's Voyager, built on HNSW; the predecessor was their open-sourced Annoy). Approximate is not a compromise you tolerate, it is the only thing that works at 100M vectors. An exact search compares the query against every track on each call, which no latency budget survives. ANN instead walks a navigable graph and returns the closest few in a sublinear probe, accepting that it will occasionally miss a true neighbor. The follow-up is the trade between recall, latency, and memory. Tune the graph so recall stays high enough that the ranker still sees the relevant tracks. When the vectors outgrow one host, shard the index and probe shards in parallel. Voyager's pitch over Annoy was faster probes at the same recall and lower memory, which is why Spotify migrated.
  • The ranker is BaRT, a contextual multi-armed bandit, not a plain gradient-boosted sort. A bandit fits because recommendation is a feedback loop. Show only the highest-predicted tracks and you never learn whether the uncertain ones would have worked. The system slowly narrows to the same safe songs. BaRT balances exploit (highest predicted 30-second-stream probability) against explore (tracks it is unsure about, shown to gather data), usually epsilon-greedy, often with a neural model estimating the per-candidate reward. The probe that exposes a weak design is "what are you optimizing," and the answer is the 30-second stream, not a click, because a click followed by an instant skip is a near-worthless label. On Home, BaRT also chooses shelf order and writes the explanation under each row.
  • The interaction pipeline behind all of this is a managed event bus into a batch and stream framework. Spotify runs Google Cloud Pub/Sub (load-tested around 2,000,000 messages per second) into Scio on Dataflow, with Cassandra serving listener profiles. Plays arrive in unbounded bursts that cannot block playback, so you reach for a queue plus a stream/batch processor. Events land in Pub/Sub and get aggregated asynchronously into the features and training data the models read. The failure mode to name is delivery semantics: the bus is at-least-once, so the aggregation has to be idempotent (de-duplicate by event id) or the same play inflates a track's signal twice. We consume this pipeline's output; designing it is a separate problem.

Sort the genuinely Spotify pieces from the standard ones. The distinctly Spotify parts are the three-model embedding stack (collaborative plus audio plus text into one space) and the Voyager ANN index that makes learned similarity servable without live ML. The bandit ranker and the event pipeline are well-understood components doing a familiar job. Retrieve broad from a vector space, rank narrow toward a real reward, and feed it all from an asynchronous event log.

Potential deep dives

The signal lives here. The interviewer picks a thread and pushes until something gives. The richest thread is that Spotify answers "what is similar" three different ways at once, and each way exists to cover the others' blind spots.

1) Three ways to say "similar," and why cold start needs all three

Collaborative filtering, the audio model, and the NLP model define "related" with different evidence, and being able to hold all three, and say when each wins, is the centerpiece of the round. Walk through them as a contrast, holding the philosophies side by side.

GoodCollaborative filtering: relatedness from co-listening

Approach

Factor the listener-track interaction matrix into low-dimensional latent vectors with logistic matrix factorization on implicit feedback. Plays and saves are positives weighted by confidence, fast skips are negatives. Two tracks the same people play land near each other; a listener lands near the music people with overlapping taste play. Similarity is a dot product in the latent space, and recommendation is a nearest-neighbor lookup from the listener vector. This is the strongest single signal because it captures real human taste. It finds connections no audio or text feature would, like a folk song and a hip-hop track that the same people happen to love.

Challenges

It is blind to anything new. A track uploaded this week has no plays, so it has no row in the matrix and no vector. This model can never recommend it, however good it is. The same goes for the long tail of obscure tracks with a handful of plays: their vectors are noisy and unreliable. Collaborative filtering also tends to reinforce popularity, since heavily-played tracks have rich, confident vectors and pull recommendations toward themselves. On its own it would make Discover Weekly a popularity echo and would never surface a fresh release.

GoodAudio model: relatedness from the waveform

Approach

Run a convolutional neural network over each track's spectrogram and emit a content embedding in the same shared space. Because the only input is the raw audio, every track gets a vector the moment it is uploaded, with zero listening history. Two tracks that sound alike, similar tempo, key, timbre, energy, land near each other even if no one has co-listened to them yet. This is the cold-start fix: a brand-new release is placed next to the music it resembles and becomes recommendable on day one, which is exactly what a 100M-track catalog with constant uploads demands.

Challenges

Audio similarity is a weaker taste signal than co-listening for tracks that have plays. Two songs can sound nearly identical and appeal to completely different people, and the waveform cannot tell you that. So the audio model is a coverage and fallback layer, not the primary ranker: it shines on cold and long-tail tracks and quietly defers to collaborative filtering once a track accumulates real listening history. Treating its embeddings as equal to the collaborative ones for popular tracks would dilute recommendation quality.

GoodNLP model: relatedness from the text around music

Approach

Embed the language that surrounds tracks, playlist titles, blog posts, reviews, articles, into term vectors so tracks people describe with the same words land near each other. This catches relatedness the other two miss. Cultural and thematic links ("rainy day," "pre-workout," a regional scene) show up in how people talk about music, and not in its waveform or co-listening pattern. It is especially useful for connecting tracks that belong to the same context even when their audio and their audiences diverge.

Challenges

It is the noisiest of the three and the most dependent on tracks actually being written about. A song nobody has put on a titled playlist or blogged about has thin text signal, and the model can be fooled by ambiguous or promotional language. So it adds coverage rather than carrying recommendations alone. You run all three because their blind spots barely overlap. Collaborative filtering misses cold tracks, the audio model misses taste nuance, and the text model misses tracks nobody writes about. Together they cover far more of the catalog than any one.

The Spotify angle
The audio CNN is the least obvious of the three models and the most unmistakably Spotify. A pure collaborative recommender is excellent, and plenty of products ship exactly that. Spotify cannot. Its catalog grows by enormous numbers of tracks no one has played yet. A model that only recommends what people already stream would bury every new artist and turn Discover Weekly into a chart in disguise. So Spotify trains a CNN on the raw audio to place cold tracks in the same space as the music they sound like. The capability traces back to its 2014 Echo Nest acquisition. The point to land: the audio model exists to solve cold start, and it is a fallback that defers to co-listening once a track has plays.

2) From 100M vectors to a few hundred candidates: ANN

Once every track is a vector, recommendation is "find the tracks nearest this listener." The naive version of that is the thing you must never ship. Be ready to say why in one breath.

The problem in one sentence: with more than 100 million track vectors, finding a listener's true nearest neighbors exactly means computing the distance to every single track on every request. That is hundreds of millions of dot products per query, impossible inside any latency budget, online or batch.

BadExact nearest neighbor: scan every vector

Approach

For each listener, compute the distance from their taste vector to all 100M+ track vectors, sort, take the top few hundred. Simple, exact, correct.

Challenges

It is linear in the catalog per query. A single recommendation touches every vector in the corpus, so the cost grows with the catalog. No budget, batch or online, can afford hundreds of millions of distance computations per listener. Multiply by 600M listeners for the weekly batch and the arithmetic is absurd. Exactness here buys nothing the product can feel, because the listener cannot tell their 47th-nearest track from their 50th, so paying linear cost for exactness is pure waste.

GreatApproximate nearest neighbor: Annoy, then Voyager (HNSW)

Approach

Build an index that returns the nearest few hundred tracks in a sublinear probe by giving up exactness you cannot perceive anyway. Spotify's original library was Annoy ("Approximate Nearest Neighbors Oh Yeah"), which builds a forest of random-projection trees that partition the space so a query only descends a few branches instead of scanning everything. Its successor, Voyager, is built on HNSW (hierarchical navigable small world) graphs. Vectors become nodes in a layered graph. A query greedily walks from a coarse top layer down to fine ones, hopping toward closer neighbors. Both turn a 100M-vector search into a short traversal, and both let all three model families share one index because they all wrote into one space.

Challenges

Approximate means you sometimes miss a true neighbor, so the dial is recall versus latency versus memory: a denser graph recalls more but probes slower and costs more RAM. Tune recall high enough that the ranker still sees the genuinely relevant tracks, and accept the rare miss. When the vectors outgrow one host you shard the index and probe shards in parallel, merging results. Voyager's reported wins over Annoy were faster probes at equal recall and lower memory, which is what justified the migration; the trade-off you are managing did not change, the operating point got better.

The Spotify angle
Naming "an ANN index" is generic. Spotify wrote and open-sourced its own, twice, and knowing that is the edge here. Annoy came out of Spotify in 2013 and powered Discover Weekly and Home for roughly a decade. Voyager replaced it in production from 2022 and was open-sourced in 2023. Say "Spotify uses Voyager, built on HNSW, after a decade on their own Annoy library." That does two things in the room. It shows you know the actual stack rather than a generic FAISS hand-wave. And it opens the migration as a real engineering decision: the same recall at lower latency and memory, which is the recall-versus-latency-versus-memory trade-off worth reasoning through carefully. The ANN index is the one component where Spotify is the source, so name it.

3) Why the ranker is a bandit: BaRT, explore, and the 30-second reward

Retrieval hands over a few hundred candidates; something has to order them and decide which 30 ship. The instinct is a relevance sort, and understanding why Spotify reaches for a bandit instead is the subtlest part of the design.

Start with the reward, because it shapes everything. A recommendation counts as right when the listener streams the track past about 30 seconds. That threshold is deliberate: a click or a play that is skipped in five seconds is a near-worthless or negative signal, while a 30-second-plus stream is real evidence the listener wanted the track. So the ranker is trained to maximize the probability of a 30-second stream, not clicks, and the interaction log's msPlayed field is what makes that label computable.

BadPure relevance sort: always show the highest-predicted tracks

Approach

Score each candidate's 30-second-stream probability with a model, sort descending, take the top 30. Ship the safest, highest-confidence tracks every week.

Challenges

It strangles its own learning. The model only sees outcomes for tracks it already ranks highly. It never gathers data on the uncertain candidates that might be a listener's next favorite, and the recommendations narrow week over week into the same safe, familiar songs. That is the opposite of discovery, which is the entire job of Discover Weekly. It also punishes new and cold tracks structurally. With no engagement history they get low confidence, so they never get shown, so they never earn the history that would raise their confidence. That feedback loop buries exactly the fresh content the product promises.

GreatBaRT: a contextual bandit that explores

Approach

Treat each candidate as an arm of a multi-armed bandit and balance exploit against explore. Exploit means showing tracks with the highest predicted 30-second-stream probability; explore means deliberately showing some tracks the model is uncertain about, to gather the data that improves future predictions. Spotify's BaRT (Bandits for Recommendations as Treatments) does this, reportedly with an epsilon-greedy strategy, often paired with a neural model estimating each arm's reward and context. The explore arm gives a fresh or niche track a real chance to prove itself. It keeps Discover Weekly feeling new instead of converging on your safest 30 songs. On the Home page BaRT also orders the shelves and writes the explanation text under each one.

Challenges

Exploration costs short-term engagement: a track shown to learn about it sometimes flops, so you are spending some user satisfaction now to buy better recommendations later, and tuning epsilon is tuning that exact trade-off. Too much explore and the playlist feels random; too little and it ossifies. Bandits also need the reward signal to flow back quickly and cleanly, which leans on the interaction pipeline being reliable and the 30-second label being honest. And explainability is a feature here, not a nicety: because BaRT writes the "Because you listened to..." reason, the candidate's source attribution from retrieval has to survive all the way to ranking.

The Spotify angle
BaRT is a bandit, not a ranker, and the explore arm is the product. Plenty of recommenders sort by predicted engagement. Spotify's whole premise is surfacing music you have not heard, and you cannot do that by only showing what the model is already sure about. So explore versus exploit is not an optimization detail bolted on at the end. It is the reason Discover Weekly works as discovery rather than as a greatest-hits loop. Three things make this Spotify's ranker specifically: it explores, it rewards a real stream (the 30-second threshold, not a click), and it explains itself in the text under each Home shelf. A sort gives you none of those.

4) A playlist is a sequence, not a top-30 set

The ranker gives you scored candidates, but Discover Weekly is an ordered playlist a person presses play on and listens through, so the last stage has its own problems that a relevance sort ignores.

Two failure modes show up the moment you take the top 30 by score. First, redundancy: the highest-scoring tracks for a listener who loves one artist can be 30 songs by that artist, or 30 near-duplicates, which is technically relevant and useless as a week of discovery. Second, no shape: a flat relevance gradient front-loads the strongest tracks and lets the playlist sag, when a good playlist eases in, varies energy, and holds attention across all 30.

So the sequencer does three jobs after ranking. It filters every track the listener has already heard, since Discover Weekly promises unfamiliar music, which means it reads the listener's play history as a final exclusion set. It diversifies, capping how many tracks come from one artist and spreading the 30 across the different facets of the listener's taste. That is usually a cheap penalty on candidates too similar to ones already placed, scored by embedding distance or by genre. And it orders, arranging the survivors so the playlist has a deliberate flow rather than a monotonic score descent. Keep this stage cheap and keep it last: it is a polish pass over an already-good ranked list, far lighter than a second ranker. The distinction it draws, between scoring tracks and building a playlist, is what this stage is for.

The de-duplication-across-weeks requirement folds in here too. Because Discover Weekly refreshes weekly, the sequencer also excludes tracks recommended in recent weeks, so the listener does not see the same "discovery" twice. That is a per-listener exclusion set keyed on recent recommendation history, cheap to maintain and exactly the kind of detail that separates a real playlist system from a generic top-K ranker.

5) Offline training, online serving, and the weekly refresh

A natural follow-up: you cannot train three models and factor a 600M-by-100M matrix inside a request, so how is any of this servable? The answer is the split that makes most large embedding systems work, and saying it crisply lands well.

Training is offline and in bulk. Factoring the interaction matrix, running the audio CNN over the catalog, and embedding the text are heavy batch jobs. They run on a cadence over Spotify's data backbone (Pub/Sub feeding Scio on Dataflow, with listener profiles served from Cassandra). They write vectors into the shared space and load them into the Voyager index. At generation or serving time there is no training and no model fitting, just a vector probe and a comparatively light ranking pass. That is the entire reason a 100M-track recommender answers fast: the expensive learning already happened, and the request path is a lookup plus a rank.

The cadence is where Discover Weekly and Home differ. Discover Weekly is a weekly batch: vectors and playlists refresh on a roughly weekly schedule, which is why your playlist changes every Monday and not continuously. That cadence is a deliberate freshness-versus-cost choice, since re-factoring the matrix and re-embedding the catalog constantly would be enormously expensive for little perceptible gain on a weekly-discovery product. The Home page sits at the other end. It reads the same precomputed vectors but ranks them live with current context (time of day, what you just played), so it feels responsive between the weekly refreshes. One stack, two tempos, and the offline-versus-online boundary is the thing to draw clearly when the interviewer asks how it all stays both smart and fast.

Flag where it is heading. Spotify has been building semantic IDs for its catalog, compact identifiers that fuse textual and behavioral signals, so large language models can reason over its entities for personalization. Treat that as the emerging direction rather than the shipped Discover Weekly path, but naming it shows you know the recommendation stack is still moving.

What is expected at each level

Spotify keeps its level ladder private rather than mapping it to clean public numbers the way some companies do, so recruiters calibrate loosely to mid, senior, and staff and the loop scales with it. Two things to know going in. The design weight rises with level. Spotify also pairs this round with a case-study round. You triage a broken production system, decide which metrics to check, and narrate how you would talk to stakeholders. Communication weighs as much as code. The bar below is for the design round itself.

Mid-level

Get the funnel right and defend it. User-driven item retrieval, not a social feed; build a taste vector, retrieve nearby tracks, rank them, sequence the final list. Draw the boxes, explain why ranking runs on a few hundred survivors while retrieval handles the whole catalog, and name a real reward (the 30-second stream). The interviewer drives the deep parts. The bar is a coherent skeleton you stand behind with a nudge or two, including at least one clear reason the system needs more than collaborative filtering.

Senior

Depth carries it. Raise the three-model contrast unprompted (collaborative filtering, audio CNN, NLP) and tie it to cold start: name which blind spot each model covers and why running all three beats any one. Reach the ANN dive on your own, exact-scan as the thing you never ship, approximate as the only thing that works, and the recall-versus-latency-versus-memory dial. Explain why the ranker is a bandit and not a sort, and what the explore arm buys. Walk the offline-training, online-serving split and the weekly cadence.

The bar is a near-complete design where you found the hard parts, cold start and the explore-exploit trade-off, on your own. Most senior candidates are sorted right here.

Staff+

Lead across the full surface. Explain why the three generators stay separate instead of merging into one model. Weigh the cost of re-embedding the catalog against the freshness it buys, and show how exploration counters the feedback loop a pure relevance ranker falls into. Place the boundaries: where recommendation ends and the playback CDN, the event pipeline, and the search stack begin. Talk about how this surface relates to the rest of Spotify's personalization, the shared embedding space, the same retrieve-then-rank shape behind Home, Release Radar, and Daily Mixes. The bar is that you drive the room while the interviewer pushes back rather than rescues.

Key Takeaways

  • ✓Discover Weekly is user-driven item retrieval, not a social feed. Build a taste vector per listener, retrieve nearby tracks from a shared embedding space, rank the pool, and sequence the final 30. Generate broad and cheap, rank and sequence narrow and smart. Say "this is retrieval over a vector space, not a fan-out" in the first two minutes.
  • ✓The centerpiece is three models voting into one space. Collaborative filtering (logistic matrix factorization on plays and skips) is the primary taste signal, but it is blind to cold tracks. An audio CNN over spectrograms places brand-new uploads by how they sound, which fixes cold start. An NLP model adds text-based coverage. They cover different blind spots, so you run all three and let the ranker arbitrate. Naming why the audio model exists at all is the senior move.
  • ✓Retrieval over 100M+ vectors must be approximate: an exact scan compares against every track per query and dies on cost, while ANN returns the nearest few hundred in a sublinear probe. Spotify wrote its own, Annoy and then Voyager (HNSW), so name it and reason about the recall-versus-latency-versus-memory dial.
  • ✓The ranker is BaRT, a contextual bandit, not a relevance sort. It optimizes for a 30-second stream rather than a click, and its explore arm is what keeps Discover Weekly surfacing music you have not heard instead of converging on your safest songs. Training is offline and in bulk, serving is a fast lookup-plus-rank, and the weekly refresh is a deliberate freshness-versus-cost choice.
Had your Spotify interview?Tell us what they asked. Reviewed before it goes in, never with your name.
Spotify interview guide

Know what Spotify asks before you walk in.

11 coding · 5 system design · 23 behavioral questions

Get Premium$49 a year · Cancel anytime
Or start freeNo card.

Get their questions first.

InterviewLegend is independent and not affiliated with or endorsed by any company it covers.