Free full guide

Top K Songs (Spotify)

Design Spotify's Top-K most-played songs feature — compute top charts over sliding time windows at listening-event scale.

00

Practice checkpoints

The interview rhythm stays compact, so the page can spend attention on the actual design decisions.

  1. 01
    Clarify scope
  2. 02
    Requirements + scale
  3. 03
    API + data model
  4. 04
    Draw architecture
  5. 05
    Deep dive
  6. 06
    Trade-off decision
01

Requirements that shape the design

Do not only state requirements. Ask for them. Each card pairs the design constraint with a clarification question you can say out loud before drawing the architecture.

Functional requirements

01What windows do charts need — arbitrary ranges, or fixed hour/day/month?

Fixed tumbling windows: past hour, day, month, and all-time. Arbitrary ranges are out of scope — that single constraint is what makes the design tractable.

02How big can K get?

Up to 1,000 — small enough that the answer is always a precomputed list, never a scan.

03How fast must a new play show up in the chart?

Within about a minute — near-real-time, not instant.

04Two songs tie at rank K - what breaks the tie?

A deterministic tiebreak (count, then song id) - the same query must return the same list twice; charts cannot flicker.

05Region and genre charts - now or later?

Later, and priced honestly: every added dimension multiplies per-window state, so the base design names that cost up front.

06A song gets taken down - when does it leave the chart?

Immediately via a serve-time denylist filter - removal never waits for counts to be recomputed.

Out of scopeArbitrary time ranges (from/to queries) · Per-user or per-region personalized charts · Play-fraud detection

Non-functional requirements

01What event rate should ingestion plan for?

Hundreds of thousands of play events per second at peak — the write path is a stream, not database inserts.

02How fast must a chart read be?

Tens of milliseconds — charts are precomputed and cached; a read never touches raw counts.

03Do the numbers have to be exact?

Ask — it changes the design. Main path here: exact counts. If memory matters more than exactness, a Count-Min Sketch shrinks state by orders of magnitude and the chart tolerates its small overcount.

04What happens when the aggregator crashes?

Replay from the event log plus checkpoints — at most the current window is recomputed; nothing is lost.

05What about one song taking half of all plays?

Partition by song ID with hot-key salting for the head of the distribution — one hit song must not melt one partition.

Keep asking — the interview is a conversation

Real interviews probe far more than a tidy list. These are the scope questions that separate candidates who interrogate the problem from those who recite it.

  • Fixed windows only, or arbitrary ranges? The answer decides the whole storage layout.
  • Must counts be exact (billing/royalties) or is a small bounded overcount fine (charts)?
  • How fresh — sub-minute, or is a minute fine?
  • Global chart only, or per-region charts multiplying every window?
  • How long is all-time — does it ever reset?
02

Numbers that force architecture decisions

Treat every estimate as a pressure that justifies a component: cache, queue, partition, replica, worker pool, or fallback path.

01

Event ingest rate

Tens of billions of plays per day (a deliberate 70B/day stress ceiling — an order beyond today’s streaming volumes)70B ÷ 86,400 s ≈ 810K events/s

Only a partitioned log absorbs this; the aggregator consumes in parallel per partition.

02

Pre-aggregation win

Aggregator batches counts per song per minute before writinga song played 10,000×/min → 1 write instead of 10,000

Stream pre-aggregation cuts store writes by 3-4 orders of magnitude for hot songs.

03

Exact-count memory

~100M distinct songs × counter + key ≈ 50 B100M × 50 B ≈ 5 GB per window

Exact counting is affordable here — which is why it is the main path, not the sketch.

04

Sketch alternative

Count-Min Sketch, 4 hash rows × 2M buckets × 4 B≈ 32 MB per window vs 5 GB exact

When per-window memory matters (many windows, many regions), the sketch trades a bounded overcount for ~150× less state.

05

Chart refresh cost

Top-1,000 maintained with a min-heap over count updatesheap update O(log 1,000) ≈ 10 comparisons per counted song-minute

Maintaining the chart continuously is cheap; recomputing it from scratch per query would not be.

Decision example

The numbers

Eight hundred thousand plays a second coming in, and the product question is tiny: the top 1,000 songs for four fixed windows, fresh within a minute.

My choice

I would land every play in a partitioned event log, aggregate exact per-song counts in one-minute batches inside the stream processor, and roll minutes into hours, hours into days — each window keeps its own counts. A small heap per window maintains the top 1,000 continuously, and the API serves a cached snapshot that refreshes every minute and pre-warms at window boundaries.

Avoid

What I would NOT do: count plays with database increments — 810K increments a second is a write storm the pre-aggregating stream absorbs for free. I also would not reach for a Count-Min Sketch by default: exact counts cost about 5 GB per window here, which is affordable, and exact numbers keep the door open for royalty-grade uses. The sketch is the right tool when window count multiplies — per-region charts, dozens of windows — and a bounded overcount is acceptable.

Change if

If the product adds per-region and per-genre charts (hundreds of windows), I would flip the head of the distribution to exact counters and the long tail to a Count-Min Sketch — hot songs stay exact, the tail costs almost nothing.

03

Architecture path

Draw the write path and the read path as separate lanes — they carry different traffic and justify different components.

Top K Songs (Spotify) serviceWrite path — count, then rankPlayer ClientEvent Log(partitioned)Aggregator (1-minbatches)Window CountsTop-K HeapRead path — serve the snapshotClientChart APICache (per window)ChartSnapshotBatch Reconcilernightly recountservicestorequeue / logexternalasync

Plays flow left to right; the heap keeps the answer warm continuously. Dashed = off the real-time path: a nightly batch recount over the log corrects any drift.

Pre-aggregation inside the stream turns 10,000 plays of a hit song into one counted write; the heap updates as counts change.

Reads never see raw counts. Snapshots refresh each minute and pre-warm at window boundaries, so the top-of-the-hour request is as fast as any other.

04

API and data model

Before optimizing, make the contract inspectable: endpoints, entities, ownership, retries, and state.

GET/charts/top?window={hour|day|month|all}&k=100

res200 [{ song_id, plays }] (≤1,000 entries)

Serves the latest ChartSnapshot from cache — cache key charts:{window}:{window_start}, warmed on window roll-over so the boundary minute is never slow.

STREAMplay-events topic (partitioned by song_id + hot-key salt)

resconsumed by the windowed aggregator

The only write path. Producers fire-and-forget; durability comes from the log, not the producer.

Core entities

PlayEvent

song_id · user_id · played_at

Append-only stream record; the log (with retention) is the source of truth for replay.

WindowCount

song_id · window (hour/day/month/all) · window_start · plays

Exact per-window counts, aggregated at coarser grains as windows roll up (hours → days → months).

ChartSnapshot

window · window_start · top_k: [song_id, plays][]

The precomputed answer the API serves; refreshed each minute, cached with a TTL.

05

Deep dive directions

Pick one lane for the final third of the interview. Each lane gives you the topic, the interviewer question it should answer, and the failure mode to avoid.

01

FocusExact or approximate

AskWhen is a Count-Min Sketch the right call here, and what exactly do you give up — in numbers?

AvoidReaching for the sketch by reflex — at 5 GB per window, exact counting is affordable and strictly more useful.

02

FocusOne song eats the stream

AskA hit single takes 30% of all plays. What happens to its partition, and how do you spread it without corrupting counts?

AvoidSalting the partition key but forgetting to merge the salted counts back into one song total.

03

FocusThe window boundary

AskIt is 00:00:01 and everyone asks for the new hourly chart. Where does that answer come from?

AvoidComputing the chart on the first request after roll-over — pre-warm the snapshot instead.

04

FocusThe aggregator dies

AskThe stream processor crashes ten minutes into an hour window. What do the charts show, and how do counts recover?

AvoidCounting in processor memory without checkpoints — replay from the log is the whole safety story.

05

FocusWhy not a time-series database

AskPrometheus-style time-series engines store counts over time — why do they fit badly here?

AvoidIgnoring cardinality: 100M song IDs as tag values is exactly what time-series engines choke on.

Ready to practice?

Talk through Top K Songs (Spotify) out loud and get AI scoring on the explanation.

Practice this with AI →