A leaderboard is a sorted data structure problem wearing a game's clothes. The interview tests whether you reach for the right structure, do the per-op math, and know the moment it stops fitting on one node.
Requirements and capacity
1M players; score updates every few seconds during a match → ~50k updates/s sustained at peak; reads: top-100 page ~5k QPS, "my rank" ~2k QPS; p99 read <50ms; persistence required across restarts. Capacity planning first (capacity-planning-the-math): updates at 50k/s, reads at 7k/s — the ratio is write-heavy, which is backwards for most systems and tells you where the money goes.
The core structure: a sorted set
Redis sorted set: ZADD lb:season {score} {player_id} is O(log n) — at 1M members that's ~20 comparisons per update. A single Redis instance does 100k–1M ops/s, so 50k updates/s and 7k reads/s fit on one node — that is the first answer, and it is the right one. ZREVRANGE 0 99 serves the top-100 in O(log n + 100); ZREVRANK {player_id} gives exact rank in O(log n). Memory: 1M members × (~10B id + 8B score + ~60–100B zset overhead) ≈ 80–120MB — trivial. State these numbers; the capacity math is the point.
Write coalescing
A player updating 10x/s only needs their latest score written: coalesce in the game service (one update per player per second in flight), cutting effective write rate ~5x. Small change, big effect on the node's headroom — interviewers listen for it.
Durability
The sorted set is the live system of record; snapshot to a DB hourly (or journal the updates) so a restart rebuilds in minutes, not hours. The zset is rebuilt by replay — the DB table player_scores(player_id, score, updated_at) plus the journal is the recovery story. State the RPO: the last hour of score changes is the loss window.
Sharding (when one node stops being enough)
Split by score band (e.g., four shards by percentile) or by player hash. A global top-100 then requires each shard to return its top-k and the read API to k-way merge — the merge is the price of sharding. Exact global rank gets expensive: shards must agree on tie-breaking to merge ranks correctly. Alternatives: approximate rank (score bucket + offset) or exact rank within tier. Say the threshold where you shard (single-node ops ceiling minus headroom) rather than the shard count.
Tie-breaking
Score collisions are guaranteed at scale. Encode score × 1e6 + submission_sequence, or compare (score, earlier-timestamp) — decide once, enforce in the structure, and never let the merge path reinterpret it.
Tradeoffs and bottlenecks
Exact vs approximate rank past one node; single-node simplicity vs merge complexity; freshness of the top-100 page (cache it with a 2–5s TTL — nobody needs 1s freshness on a ranking page); snapshot write amplification; and the single hot zset key, which is fine at 1M players and the first thing you fix at 10M.