A leaderboard looks like a database sort with a LIMIT. It is not, and the reason is one query: a player wants to know their own rank. Answering that fast, for millions of players, updated live, is where the design gets interesting.
Why the database sort fails
The obvious approach is a scores table with an index on score. Showing the top 100 is easy, just sort descending and take 100. But now a player ranked 4,000,012th opens the app and asks where they stand. To compute that rank, the database has to count how many players have a higher score, which means scanning or counting a huge slice of the table. Do that on every profile view for millions of users and your database melts. Worse, scores change constantly, so any precomputed rank is stale the moment you write it.
The Redis sorted set
The right tool is a sorted set, the data structure Redis calls a ZSET. It stores members (player IDs) each with a score, and it keeps them ordered by score automatically. What makes it perfect for leaderboards is the set of operations it supports in logarithmic time:
- Update a score:
ZADD board 5000 player42. It reinserts the player at the correct position in one call. - Get the top N:
ZREVRANGE board 0 99 WITHSCORESreturns the highest 100. - Get a player's rank:
ZREVRANK board player42returns their position directly.
That last one is the whole game. The rank query that would scan a database is a single fast operation on a sorted set, because the structure maintains ordering as an invariant. Getting your own rank, the page around your rank, and the top of the board are all cheap.
Under the hood a ZSET uses a skip list plus a hash map, which is why both "where does this member rank" and "who is at rank K" are fast. You get the ordering of a balanced tree with the point-lookup speed of a hash.
Handling scale with sharding
One Redis instance holds a lot, but a global board with tens of millions of active players and constant writes can outgrow a single node's memory and throughput. The usual move is to shard, and the clean way to shard a leaderboard is by segment rather than by hashing player IDs.
Most leaderboards are naturally partitioned already: per region, per game mode, per weekly season. Each of those is a separate sorted set, often on a separate instance, and each is independently small and fast. A player's rank within their own board is a local query. The hard case is a single global ranking across shards, because a player's global rank depends on every shard. You handle that either by accepting an approximate global rank or by periodically merging shard tops into a global board. Which brings us to the real scaling insight.
The approximate-rank trick
Exact rank for a player buried in the middle of tens of millions is expensive to keep globally consistent, and nobody actually needs it. The difference between rank 4,000,012 and rank 4,000,050 is meaningless to the user. So a common optimization is to bucket scores into ranges and report an approximate rank ("top 15 percent") for players outside the top tier, while keeping exact ranks only for the top few thousand where precision matters and competition is real. This turns a hard distributed-counting problem into a cheap histogram lookup.
Durability and the trade-off
Redis holds the board in memory, so you back it with a durable store. The source of truth for scores lives in your primary database; Redis is the fast serving layer you rebuild from that source if a node is lost. The trade-off across the whole design is memory for speed: you are keeping the ordered index resident in RAM because the alternative, computing ranks from a disk-based store on demand, cannot meet the latency a live leaderboard needs.
How the real systems do it
Game platforms and apps from mobile titles to fitness trackers lean on Redis sorted sets for exactly this reason, and managed offerings build leaderboard APIs directly on the ZSET primitives. The pattern is consistent: sorted set for the live index, segment sharding for scale, exact ranks at the top, approximate ranks in the long tail, and a durable database as the system of record.
I wrote the full breakdown, with diagrams and the data model, here: https://www.systemdesign.academy/interview/design-leaderboard
Top comments (0)