Design a leaderboard and top-K: the system design interview walkthrough
The request to design a leaderboard sounds like one line — track everyone's score, show the top ten — and that one line hides the whole interview. Do it for a thousand players and you reach for a sort(); do it for hundreds of millions of players, updated thousands of times a second, returning the top-K and any one player's exact rank instantly, and it becomes a sorted-state-at-scale problem with a dozen sharp edges. This is one of the most approachable-looking leaderboard system design prompts, and exactly why it is a trap: the easy framing hides that top-K system design at scale is about keeping a huge set sorted under a relentless write rate, not about sorting a list on demand.
The round turns on one structural insight. A relational SELECT ... ORDER BY score DESC LIMIT K re-sorts (or rescans an index) on every read and buckles under a high update rate; a live leaderboard instead keeps its scores in a sorted set — a skip list, as in a Redis ZSET — so a score update is O(log n) and reading the top-K or a single player's rank is O(log n) too. Get that one reframe — the leaderboard is a sorted set, not a table you re-sort — and the rest of the system design interview is deciding how to shard it, how to answer real-time ranking queries like "my exact rank" without re-counting the world, and how to keep the fast in-memory board durable and correct. This walkthrough follows the path a strong candidate draws on the whiteboard — requirements, estimate, API and data model, high-level design, then the deep dives — and at each step it names the follow-up the interviewer uses to tell a memorised answer from an engineer who understands the tradeoff.
How the leaderboard round is actually run
A leaderboard round runs about 60 minutes on a shared canvas — you draw the boxes and arrows while you talk, and the interviewer interrupts. It is rated Medium, and that is the trap: the problem is approachable enough that candidates relax, reach for a database table with an index, and never notice that the write rate and the exact-rank query quietly break it. The moment you draw "store scores, sort them," the real question arrives — how do you keep this sorted at scale and still answer "what is my rank" instantly — and every answer after that compounds from the sorted set.
The interviewer rarely lets "store the scores and sort them" stand. "Scores update thousands of times a second and you need the top 100 live — why not just
SELECT ... ORDER BY score DESC LIMIT 100? Walk me through what happens to that query under the write rate, and how you'd return one player's exact rank without scanning everyone above them." That question — sorted state under write pressure — is the one a copied answer can never rehearse, and the one this round is really testing.
The rounds here are modelled on how the loop runs, not scraped from any candidate's recall. What matters for prep is the shape: a blank canvas, a clock, and a follow-up after every decision you make.
The five-step framework for any HLD round
Every high-level design answer fits the same spine. Say it out loud at the start so the interviewer knows you have a plan, then walk it:
- Clarify requirements — functional and non-functional, and what you are explicitly not building.
- Estimate — player count, update rate, read QPS and entry size, to justify every later choice.
- High-level design — the boxes and arrows: clients, score service, sorted-set store, durable log, query API.
- Deep dive — the two or three decisions that actually decide this system.
- Follow-ups — bottlenecks, failure modes, and what you would change at ten times the scale.
What the debrief measures
The score is not "right or wrong design." A good HLD debrief reads on four dimensions, and every move below maps to one: problem solving (did you reach a sorted set, then shard it, then separate top-K from exact global rank), communication (did you say why a sorted set beats ORDER BY LIMIT in a sentence), technical execution (ZADD/ZRANK at O(log n), scatter-gather top-K, event log plus TTL'd windows), and time management (did you spend your 60 minutes on the sorted set, sharding, and durability, not on the score-validation rules). Reading a walkthrough teaches the design; it does not teach you to deliver it on the clock while someone pushes back.
Step 1 — Requirements: what a leaderboard must do
Spend the first few minutes here. Jumping to boxes before you have scoped the problem is the fastest way to design the wrong system.
| Type | Requirement |
|---|---|
| Functional | Submit or update a player's score; return the top-K players; return a given player's exact rank; return the window of players around a given player ("around me"); scores update in real time and reads see them quickly. |
| Functional (optional) | Time-windowed boards (daily / weekly / all-time); tie-breaking rules; multiple leaderboards per game or region — call these out, then scope most of them for later. |
| Non-functional | High write throughput (thousands of score updates/sec or more); low-latency reads for top-K and my-rank; horizontal scalability to hundreds of millions of players; durability (the board survives a node restart); high availability. |
The sentence that earns points: "The hard part isn't computing a top-ten — that's a sort — it's keeping a set of hundreds of millions of scores sorted under a heavy write rate while answering top-K and exact-rank reads instantly, so the core of the design is a sorted set, not a table I re-sort on every read." Say that and you have framed the entire design before drawing a box.
Step 2 — Capacity estimation (back of the envelope)
Estimation here is about proving one thing: the hard axis is keeping a huge set sorted under a heavy write rate with cheap rank reads, not clever sorting logic.
| Quantity | Assumption | Result |
|---|---|---|
| Players | hundreds of millions of entries | too large and too hot for an ORDER BY table; shard the sorted set across nodes |
| Score updates | thousands per second (spiky) | each update is an O(log n) sorted-set write, not a re-sort or an index rebuild |
| Read QPS | top-K + my-rank + around-me, read-heavy | serve from replicas and cache hot queries (the top-K list barely changes second to second) |
| Memory per entry | player id + score, tens of bytes | the whole board fits in RAM once sharded — which is why an in-memory sorted set is viable |
Two conclusions fall out, and you should say both out loud. First, the sorted set is the system — because updates and rank/range reads are both O(log n) on a skip-list-backed sorted set, a single node sustains a huge update rate that an ORDER BY LIMIT query never could. Second, because reads dwarf writes and the top-K list is nearly stable between updates, reads scale out on replicas and a cache, while the exact-rank query is the one read you cannot fake cheaply once the board is sharded. You have now pointed the interview at the real deep dives — the sorted set, then sharding and rank — before anyone asked.
Step 3 — API and data model
The API is deliberately small: submit a score, read the top-K, read one player's rank, read the window around a player.
| Operation | Shape |
|---|---|
submit_score(player, score) |
upsert the player's score; O(log n) on the sorted set; idempotent so a retried update can't double-apply. |
get_top(k) |
return the top K players and scores — a range read from the high end of the sorted set. |
get_rank(player) |
return the player's current rank (and score); O(log n) on one node, the expensive one once sharded. |
get_around(player, window) |
return the W players above and below a player — a range read centred on the player's rank. |
The data model is thin. The sorted-set entry is the serving unit, and a separate score event log is the durable source of truth:
| Entity | Notes |
|---|---|
leaderboard |
(id, window) — e.g. a game plus daily / weekly / all-time; each window is its own sorted set key. |
entry |
(player, score, updated_at) — one member per player in the sorted set, scored by score (ties broken by updated_at). |
score_event |
(player, delta_or_value, event_id, timestamp) — the append-only log of score changes that the sorted set is rebuilt from. |
Step 4 — High-level design: the sorted set and the event log
Now the boxes. Score-producing clients send updates to a score service, which validates them, writes them to a durable event log, and applies them to the sorted-set store; a query API serves top-K, rank and around-me reads from the sorted set (and its replicas). Around that spine:
-
Score service (ingestion): validates each update, appends it to the event log, and issues the
ZADD-style update to the sorted set — writing the durable record before (or atomically with) the in-memory update so nothing is acknowledged that can't be rebuilt. - Sorted-set store: the leaderboard itself, held as a sorted set (Redis ZSET: a skip list plus a hash), sharded across nodes when one node can't hold the players or the write rate, with read replicas for the read-heavy query load.
- Durable store / event log: the append-only history of score changes — the source of truth the sorted set is rebuilt from after a restart, and the record used for recomputation and audit.
-
Query API:
get_top,get_rank,get_around, reading from the sorted set and a cache of hot results (the top-K list is cached because it barely changes between updates).
Say explicitly that the sorted set is the serving layer, not the source of truth — the durable event log is. Keeping them separate is exactly what makes the fast in-memory board safe to lose and rebuild, and naming that split unprompted is a signal the interviewer is listening for.
Deep dive: the sorted set for real-time ranks
This is the first decision that actually decides the round. Where do the scores live, and why does that shape everything else?
Model the leaderboard as a sorted set keyed by score — the structure behind a Redis ZSET, which pairs a skip list (ordered by score, for rank and range queries) with a hash (player → score, for direct lookup). Three operations cover the whole API, and each is cheap:
| Operation | Sorted-set (ZSET) cost | Why SQL ORDER BY is worse at write scale |
|---|---|---|
Update a score (ZADD) |
O(log n) — reposition one member in the skip list |
An indexed UPDATE must maintain a B-tree index on score on every write; under thousands of updates/sec the index becomes the bottleneck and lock contention bites. |
Top-K (ZREVRANGE 0 K-1) |
O(log n + K) — walk from the high end |
ORDER BY score DESC LIMIT K re-sorts or rescans the index on every read; cheap once, ruinous at high read QPS over a huge table. |
A player's rank (ZREVRANK) |
O(log n) — the skip list tracks span counts |
SELECT COUNT(*) WHERE score > ? scans or aggregates an index range per query — it scales with how many players outrank you, not log n. |
The point is that a sorted set makes both the write and the rank read O(log n), so the structure that keeps the board sorted is the same structure that answers "my rank" — you never re-sort. A relational table can store scores, but it pays to re-derive order on every read, which is exactly the cost a live board can't afford.
"You keep saying 'sorted set' — scores update thousands of times a second and I need the top 100 live, so why not just
ORDER BY score DESC LIMIT 100?" Because that query re-sorts or rescans an index on every read, and under the write rate the index maintenance and lock contention collapse it. A sorted set updates one member inO(log n)and reads the top-100 inO(log n + 100)from state that is already ordered — no re-sorting, which is the whole reframe.
System Design
HLD · Media & analytics · 60 min
Design a leaderboard and top-K out loud, with follow-ups
Deep dive: sharding and global rank
This is the decision most candidates under-think. One sorted set on one node is clean; the follow-up is always "the board is bigger than one node now — how do top-K and a single player's exact rank hold up?"
One sorted set lives on one node, bounded by its memory and throughput. Beyond that you shard players across nodes (by hashing the player id), and the two read paths diverge sharply:
| Query | How it works when sharded | Cost |
|---|---|---|
| Top-K | Scatter-gather: ask each shard for its own top-K, then merge the shard results and keep the overall top-K. | Cheap and parallel — K is small, each shard returns O(log n + K), and the merge is O(shards × K). |
| Exact global rank | Count how many players across all shards outrank this player — each shard can give a local count, summed for the true global rank. | Expensive — it touches every shard on each query, so it does not scale the way top-K does. |
| Approximate rank | Maintain a bucketed score histogram (count of players per score band); a player's rank ≈ sum of higher bands plus their position within their band. | Cheap and good enough for "you're in the top 2%"; trades exactness for a single fast lookup. |
So top-K shards beautifully and exact global rank does not. You name the three honest ways out: keep the whole set on a beefy replica dedicated to rank queries (simple, bounded by one machine); serve approximate rank from a bucketed histogram when "top 2%" is acceptable; or periodically recompute exact ranks in the background and serve the last snapshot. Which you pick is a product call, and saying that — that exact real-time global rank at scale is a cost you trade against, not a free read — is the senior signal here.
"The board is sharded across 20 nodes — how do you tell player #4,000,000 their exact rank fast?" You can't do it cheaply as a live query: exact global rank means counting everyone above them across all 20 shards on every call. So you either keep the full set on one beefy rank replica, or serve an approximate rank from a bucketed score histogram (top N%), or recompute exact ranks periodically and serve the snapshot — and you say which, and why, out loud.
System Design
HLD · Media & analytics · 60 min
Defend sharding and exact-vs-approximate rank out loud
Deep dive: durability, time windows and correctness
Sharding and rank decide how reads scale; this decides whether the board survives and stays correct. The follow-up is always the restart case.
An in-memory sorted set is lost on restart — so it cannot be the source of truth. Back it with a durable store and treat the board as a projection:
- Rebuild path. Every score change is written to an event log (or write-through to a database) before or atomically with the sorted-set update. When a node restarts, the sorted set is rebuilt by replaying the log (or loading the last snapshot plus the tail of the log), so losing the in-memory board costs latency, not data.
-
Time windows via TTL'd keys. Daily, weekly and all-time boards are separate sorted-set keys —
lb:daily:2026-10-12,lb:weekly:2026-W41,lb:alltime— so a window "resets" simply by writing to a new key; expired daily/weekly keys are dropped with a TTL, and each window is fed from the same event log. -
Ties and late updates. Ties are broken by a secondary sort (e.g. earliest
updated_atwins), often by encoding the timestamp into the score. Late, retried or duplicate updates are handled by making score application idempotent — carry anevent_idand apply each event once — so a replayed or re-delivered event can't corrupt a player's score.
Put together: the durable event log makes the fast board rebuildable, TTL'd per-window keys make resets trivial and bound memory, and idempotent application with a tie-break rule makes the board correct under retries and restarts.
"The Redis node holding the board restarts — is the leaderboard gone, and how do daily boards reset?" No: the sorted set is only the serving layer, so it's rebuilt by replaying the score event log (or the last snapshot plus the tail), losing latency, not data. Daily boards are a separate key per day with a TTL, so a new day is just a new key and old ones expire — all fed from the same durable log.
The follow-ups that decide the leaderboard round
Scan the probes from each deep dive and they rhyme. Master these five and you are ready for a version of the question that is not drawn exactly like this one.
-
"Why a sorted set and not
ORDER BY score LIMIT K?" → The SQL query re-sorts or rescans an index on every read and its index maintenance collapses under the write rate; a sorted set keeps state ordered so updates and rank reads are bothO(log n). (communication) - "The board is sharded — how do you return one player's exact rank fast?" → You can't cheaply live; keep a full-set rank replica, serve approximate rank from a bucketed histogram, or recompute exact ranks periodically — and say which. (problem solving)
- "How does top-K work once the data is sharded?" → Scatter-gather: top-K from each shard, merge, keep the overall top-K — cheap and parallel because K is small. (technical execution)
- "The node holding the board restarts — is it lost?" → No: the sorted set is a projection of a durable event log, rebuilt by replay (or snapshot plus tail); the log, not the board, is the source of truth. (technical execution)
- "How do daily and weekly boards reset, and how do you handle ties and duplicate updates?" → Separate sorted-set key per window with a TTL; tie-break on timestamp; idempotent score application keyed by event id. (problem solving)
Each maps to a debrief dimension. Reading this list teaches you the answers; it does not teach you to deliver them while someone interrupts — that only comes from saying them out loud.
How to prepare: a one-week plan
You do not need fifty systems. You need this one cold and the framework automatic, so the next "design X" is just new nouns in the same five steps.
- Days 1–2: Learn the five-step framework until it is reflex. Do the leaderboard end to end on paper — requirements, estimate, API, data model, the sorted-set-and-event-log boxes.
-
Days 3–4: Drill the sorted-set deep dive:
ZADD/ZREVRANGE/ZREVRANKatO(log n), and whyORDER BY LIMITcan't keep up at write scale. Say each tradeoff in one sentence. - Days 5–6: Own sharding and durability — scatter-gather top-K, exact vs approximate global rank, the event log and rebuild, TTL'd time-windowed keys, ties and idempotency. Answer each of the five follow-ups out loud.
- Day 7: A full timed 60-minute round, narrating the whole way, with someone (or something) interrupting. This is the rep that counts.
This post is the deep walkthrough behind one row of the broader Top 25 system design interview questions — once the leaderboard is automatic, the same spine carries the rest of the list.
Frequently asked questions
How do I design a leaderboard and top-K step by step?
Follow the five-step HLD framework. Clarify requirements (submit/update scores, top-K, a player's exact rank, around-me, real-time updates); estimate to show the hard axis is keeping hundreds of millions of scores sorted under a heavy write rate with cheap rank reads, not sorting logic; define the small API (submit_score, get_top, get_rank, get_around) over a thin entry/event-log model; draw the design (clients to a score service that writes a durable event log and updates a sharded sorted-set store, with replicas and a query API); then deep-dive the decisions that matter — the sorted set, sharding and global rank, and durability with time windows. Finish on the follow-ups about exact-versus-approximate rank and restarts.
Why use a sorted set instead of a database ORDER BY?
Because SELECT ... ORDER BY score DESC LIMIT K re-sorts or rescans an index on every read, and maintaining a score index under thousands of updates per second becomes the bottleneck, with lock contention on top. A sorted set — a skip list plus a hash, as in a Redis ZSET — keeps the scores ordered as state, so updating one score is O(log n), reading the top-K is O(log n + K), and reading a player's rank is O(log n). The structure that keeps the board sorted is the same one that answers "what's my rank," so you never re-sort on read.
How do you return a single player's rank at scale?
On one node it is cheap: a sorted set tracks span counts, so a rank read is O(log n). The problem appears once you shard players across nodes, because an exact global rank means counting everyone who outranks the player across every shard on each call. The honest answers are to keep the whole set on one beefy replica dedicated to rank queries, to serve an approximate rank from a bucketed score histogram ("top 2%"), or to recompute exact ranks periodically and serve the snapshot — and to say which tradeoff you are making and why.
How does top-K work when the leaderboard is sharded?
With scatter-gather. Each shard holds a subset of players in its own sorted set and can return its local top-K cheaply; the query fans out to every shard, collects the partial top-K lists, merges them, and keeps the overall top-K. It scales well because K is small, each shard's read is O(log n + K), and the merge is O(shards × K). The top-K list also barely changes between updates, so it is a prime candidate to cache and serve from replicas under heavy read load.
How do you make an in-memory leaderboard durable and handle time windows?
Treat the sorted set as a serving projection, not the source of truth. Write every score change to a durable append-only event log (or write-through to a database) before or atomically with the in-memory update, so a node restart rebuilds the board by replaying the log (or loading a snapshot plus the tail) — you lose latency, not data. Daily, weekly and all-time boards are separate sorted-set keys fed from the same log, so a window resets by writing to a new key and old windows expire with a TTL. Make score application idempotent with an event id, and break ties on timestamp, so retries and duplicates can't corrupt the board.
How do I practise a system design interview out loud?
Reading a walkthrough is not the same as defending a design while someone interrupts. On Zynter, the HLD track runs live voice rounds modelled on how real system design loops run — you talk through a shared design on the canvas, and a voice interviewer pushes with the follow-ups above one at a time, then gives you a debrief on problem solving, communication, technical execution, and time management. The score is a practice signal, not a hiring decision.
Practise it on Zynter
You can describe a sorted set on paper. The round is won on sharding and rank-at-scale, out loud.
Pick the Leaderboard and Top-K round, draw the sorted-set-and-event-log design on the canvas, and defend it out loud to a voice interviewer that keeps asking "why not just `ORDER BY score LIMIT 100`?" and "the board is sharded — what's player four-million's exact rank?" — then read the debrief. Zynter.ai is on-demand mock interviews modelled on real company loops, with 150+ rounds across 20 companies including the HLD system design track (Sep 2026).
Practise the Leaderboard round →
Browse all interview tracks




Top comments (0)