Step 1 — Clarify the requirements
Never start drawing boxes. A strong candidate spends the first few minutes scoping the problem so the design that follows is justified. For a real-time leaderboard, the questions worth asking are:
- How many players, and how frequently do scores update?
- Do we need a global board, per-region, and time-windowed (daily/weekly) boards?
- Which queries: top-k, a player's rank, the neighbours around a player?
- Must rank be exact, or is an approximate percentile acceptable for huge boards?
Functional requirements
- Update a player's score and reflect it in rankings quickly.
- Return the top-k players and a given player's rank.
- Support the slice of players around a given rank.
Non-functional requirements
- Low-latency reads and writes as scores change continuously.
- Scale to millions of players and high update rates.
- Consistent rankings (or clearly bounded approximation).
Step 2 — Back-of-the-envelope estimates
Sizing the system tells you which parts are hard. Round aggressively and state your assumptions out loud; the numbers matter less than showing you can reason about scale.
| Metric | Estimate | Reasoning |
|---|---|---|
| Players | millions | A global board can be huge; exact rank over millions is the scaling challenge. |
| Update rate | high, continuous | Scores change constantly in an active game, so updates must be cheap. |
Step 3 — Data model and API
A compact data model and a small API surface anchor the rest of the discussion. Keep both minimal; you can always extend them when the interviewer pushes.
Core entities
sorted set (Redis ZSET)
member = player_id, score = points
ZADD updates, ZREVRANGE for top-k, ZREVRANK for a player's rank.
score history
player_id, score, timestamp
Durable source of truth backing the in-memory board.
API sketch
- POST
/api/v1/scores— Submit/update a player's score. - GET
/api/v1/leaderboard/top?k=— Return the top-k players. - GET
/api/v1/leaderboard/rank/{player}— Return a player's rank and neighbours.
Step 4 — High-level design
Sketch the happy path end to end before optimising anything. This is the architecture you would draw on the whiteboard first:
- 1Keep the board in a sorted set (e.g. Redis ZSET) so updates and rank queries are logarithmic.
- 2On a score change, update the sorted set; persist the score to a durable store for recovery.
- 3Top-k and rank queries read directly from the sorted set in O(log n + k).
- 4For huge boards, shard and/or approximate ranks below the top tier.
Step 5 — Deep dives that separate strong answers
The high-level design is table stakes. Interviewers spend most of the time here, probing the decisions that actually carry the system. These are the ones to be ready for.
Why a sorted set wins
Storing scores in a plain table and sorting on every read is O(n log n) per query, which collapses with millions of players and continuous updates. A sorted set (skip list plus hash, as in Redis ZSET) keeps members ordered by score with O(log n) insert/update and O(log n + k) range reads, so 'update a score', 'top-k', and 'rank of player X' are all cheap. This single data-structure choice is the crux of the answer; lead with it and explain the complexity rather than describing a database sort.
Scaling rank queries past one node
One sorted set fits a lot, but a truly massive global board outgrows a single node or makes exact rank expensive. Options: shard players across nodes (e.g. by score band or region) and merge for top-k, accepting that an exact global rank for an arbitrary player now requires combining shard counts. For the common 'top 100' query, a small global top-tier set plus per-shard locals suffices. When exact rank over tens of millions is not worth the cost, switch to approximate percentile ranking (bucket scores into histograms) for everyone outside the top tier, where users care about 'top 5%' not 'rank 4,210,118'.
Durability and time-windowed boards
The in-memory board is fast but volatile, so the authoritative score also goes to a durable store; the sorted set can be rebuilt from it after a restart. Time-windowed boards (daily, weekly, seasonal) are separate sorted sets keyed by window, expired or archived when the window closes, which also keeps each board's size bounded. Writes can be made cheaper by buffering frequent score updates and applying the latest, since intermediate scores between reads do not matter for ranking.
Step 6 — Bottlenecks and how to scale past them
Naming where the design breaks, and the specific fix, is what signals seniority. For a real-time leaderboard the pressure points are:
Sorting all players per read.
Use a sorted set: O(log n) updates and O(log n + k) reads.
Exact rank over tens of millions is costly.
Approximate percentile ranks outside the top tier; exact only for the top-k.
Hot single-node board.
Shard by region/score band; keep a small global top tier.
Step 7 — Key tradeoffs
There is rarely one right answer. State the tradeoff, then commit to a side with a reason tied to the requirements you clarified in step one.
Rank exactness
Exact rank (precise, costly at scale)
Approximate percentile (cheap, good enough)
Guidance: Exact for the top tier; approximate for the long tail where users want a percentile.
Storage
In-memory sorted set (fast, volatile)
Durable store (safe, slower)
Guidance: Both: serve from the sorted set, back it with a durable score of record.
Common follow-up questions
When you finish the core design, expect the interviewer to pull on one of these threads. Have a one-paragraph answer ready for each.
- How do you support daily/weekly/all-time boards together?
- Storing scores in a plain table and sorting on every read is O(n log n) per query, which collapses with millions of players and continuous updates. A sorted set (skip list plus hash, as in Redis ZSET) keeps members ordered by score with O(log n) insert/update and O(log n + k) range reads, so 'update a score', 'top-k', and 'rank of player X' are all cheap. Sketch the change against the high-level design above and tie your choice back to the requirements you clarified, rather than reaching for the most complex option.
- How would you return the players immediately around me?
- One sorted set fits a lot, but a truly massive global board outgrows a single node or makes exact rank expensive. Options: shard players across nodes (e.g. Sketch the change against the high-level design above and tie your choice back to the requirements you clarified, rather than reaching for the most complex option.
- How do you keep the board correct after a node restart?
- The in-memory board is fast but volatile, so the authoritative score also goes to a durable store; the sorted set can be rebuilt from it after a restart. Time-windowed boards (daily, weekly, seasonal) are separate sorted sets keyed by window, expired or archived when the window closes, which also keeps each board's size bounded. Sketch the change against the high-level design above and tie your choice back to the requirements you clarified, rather than reaching for the most complex option.
- How would you prevent score-submission cheating?
- Storing scores in a plain table and sorting on every read is O(n log n) per query, which collapses with millions of players and continuous updates. A sorted set (skip list plus hash, as in Redis ZSET) keeps members ordered by score with O(log n) insert/update and O(log n + k) range reads, so 'update a score', 'top-k', and 'rank of player X' are all cheap. Sketch the change against the high-level design above and tie your choice back to the requirements you clarified, rather than reaching for the most complex option.