Sorted sets and leaderboards
Keeping millions of scores ordered with O(log n) updates.
01The intuition
The problem it solves
A school sports day keeps a leaderboard on a whiteboard: names sorted by points. Every time a runner finishes, someone rubs out a line, moves everyone below it down and writes the new entry. With 30 runners it is fine. With 3 million players in an online game scoring every few seconds, rewriting a sorted list for every score is millions of moves a second, and "what is my rank?" means counting everyone above me.
A sorted set is a structure that keeps items ordered by a score and lets you insert, update, find a rank and read a range, each in O(log n) time. It is the whiteboard that does not need rewriting.
Build it one piece at a time
Step 1: a sorted array, and why it fails. Keep (score, player) pairs sorted. Finding a rank is a binary search, O(log n). Inserting means shifting everything after the position, O(n). For 3 million players and 100,000 score updates a second, that is 1011 moves a second. Not possible.
Step 2: a balanced tree, or a skip list. A balanced binary search tree (or a skip list, which is simpler to build and what Redis uses) keeps order with O(log n) insert and delete. A skip list is a sorted linked list with express lanes: level 1 links every node, level 2 every second node, level 3 every fourth, and so on, so a search skips ahead like an express train and drops to local lanes near the target.
| Operation | Sorted array | Skip list / balanced tree |
|---|---|---|
| update a score | O(n) | O(log n): remove, re-insert |
| rank of a player | O(log n) | O(log n) if each node stores the count of nodes it skips |
| top 10 | O(1) | O(10): walk from the head |
| players ranked 1,000–1,050 | O(1) slice | O(log n + 50) |
Step 3: add a hash map beside it. To update Rao's score you must find Rao's node. A hash map from player to node makes that O(1); the sorted structure then handles the reordering. A sorted set is exactly this pair: map plus skip list.
Step 4: store the rank counts. Each express-lane link records how many base nodes it jumps over. Rank is then computed on the way down by summing the jumps, O(log n), and "the 1,000th player" is found by the same sums in reverse.
Step 5: go beyond one machine. Three million entries at about 100 bytes each is 300 MB, which fits one machine's memory with room to spare, and a single node handles about 100,000 sorted-set operations a second. For a global game with 100 million players, shard by score range (the top band is small and hot; replicate it), or keep one sorted set per league or region and merge only for the global top 100.
Why it works, with the numbers
Logarithms are small. For 3 million players:
log2(3,000,000) ≈ 22, so an update touches about 22 nodes instead of shifting up to 3 million entries
At 100,000 updates a second that is 2.2 million node operations a second, comfortable for one core. The guarantee is that after every update the set is exactly sorted and every rank is exact. The failure modes are the hash map and the list getting out of step (the two updates must be one atomic operation), a tie rule that is not deterministic (two players on 500 points must be ordered by something stable, like who reached it first), and a single hot set that one machine cannot keep up with, which the sharding in step 5 addresses.
The trade-off you must say out loud
Exact ranks in memory on one machine, or approximate ranks (bucketed scores, periodic snapshots) across many. Say which the product needs: a player's exact position among a billion is rarely worth the cost, while the exact top 100 always is. The interviewer may ask about a weekly leaderboard; the answer is a sorted set per week keyed by week number, expired when the week is old.
Where it shows up in the 100 Google problems
- Design a real-time game leaderboard: a sorted set per board, hash map for players, sharded by score band for the global one.
- Design a stock trading system with a limit order book: bids and asks are two sorted sets keyed by price then time; the best price is the head of each.
- Design a restaurant waitlist: parties ordered by join time with priority adjustments, where "how many ahead of me" is a rank query.