Real-time leaderboards rank millions of users by score and serve instant rank queries — ranking by score, top-N lists, and "who’s near me" lookups. Games, trading platforms, and fitness apps all need them. Redis sorted sets are the canonical building block, but at massive scale the design gets more interesting.
Data Model and Core Operations
The leaderboard has a simple logical model: a mapping of user_id → score. The required operations are:
- Update score for a user (increment or set)
- Get the rank of a specific user (1-indexed, descending by score)
- Get the top-N users with their scores
- Get users around a given user (K above and K below)
- Get the score of a specific user
Redis Sorted Set Primitives
Redis sorted sets store members with a floating-point score, maintaining O(log N) insertion and rank queries via a skip list + hash map combination:
ZADD leaderboard 1500 user:42 -- set score (O(log N))
ZINCRBY leaderboard 100 user:42 -- increment score atomically (O(log N))
ZSCORE leaderboard user:42 -- get score (O(1))
ZREVRANK leaderboard user:42 -- 0-indexed rank, high score = rank 0 (O(log N))
ZREVRANGE leaderboard 0 9 WITHSCORES -- top 10 users (O(log N + 10))
ZCARD leaderboard -- total members (O(1))
All core operations are O(log N) or better. A single Redis node handles millions of members comfortably.
Score Update Strategy
ZINCRBY atomically increments a member’s score in place — no read-modify-write cycle needed. For absolute score sets (e.g., a user’s final game score), use ZADD leaderboard GT score user_id (the GT flag, available in Redis 6.2+, updates only if the new score is greater than the current). This prevents out-of-order updates from overwriting a higher score.
Rank and Percentile Queries
ZREVRANK returns a 0-indexed rank (0 = highest score). Convert to 1-indexed: rank = ZREVRANK(...) + 1.
Percentile rank tells a user where they stand relative to everyone:
total = ZCARD leaderboard
rank = ZREVRANK leaderboard user_id + 1
percentile = (1 - rank / total) * 100
-- e.g., rank 100 of 10,000 → top 99th percentile
Top-N Query
ZREVRANGE leaderboard 0 N-1 WITHSCORES returns the top N members in descending score order. This is O(log N + N) — fast even for N = 1000. Paginate with offset: page 2 of 10 = ZREVRANGE leaderboard 10 19 WITHSCORES. Deep pagination (offset 1,000,000) is slow — use cursor-based pagination anchored to a score value instead.
Around-Me Queries
To show K users above and K below the current user:
rank = ZREVRANK leaderboard user_id -- 0-indexed
start = MAX(0, rank - K)
stop = rank + K
ZREVRANGE leaderboard start stop WITHSCORES
This returns a 2K+1 window centered on the user in a single O(log N + 2K) command.
Tie-Breaking and Score Precision
Redis sorts equal scores lexicographically by member name, which is arbitrary. For stable tie-breaking (earlier achievers rank higher), encode a timestamp into the score:
-- score = base_score * 1e10 + (MAX_TS - timestamp_ms)
-- higher base score wins; among ties, earlier timestamp wins
This packs two dimensions into a single float. Verify float64 precision is sufficient for your score range (float64 has 15–16 significant decimal digits).
Sharding for Billions of Users
A single Redis sorted set starts straining at ~50–100M members. Shard by hashing user_id into S shards:
shard_id = user_id % S
key = "leaderboard:shard:{shard_id}"
Score updates go to the correct shard in O(log(N/S)). For global rank, you need ranks from all shards. Approximate approach: compute local rank within each shard, then sum ranks from shards with higher max-scores. Exact global rank requires a merge step — manageable if done offline or for top-K only.
Sliding Window Leaderboards
Weekly or monthly leaderboards require resetting or rotating scores. Two strategies:
- Key rotation: use time-bucketed keys like
leaderboard:2026-W15. Scores accumulate during the window. At rollover, create a new key. Old keys expire via TTL. - Time-decay scoring: score =
raw_score / (age_hours + 2)^1.5(similar to Hacker News ranking). Re-score on each update. Scores naturally decay — no key rotation needed, but rank order shifts even without new events.
DB Persistence and Historical Leaderboards
Redis is the hot path, but treat the relational DB as the source of truth. On each score update, write to both Redis (synchronously) and the DB (asynchronously via a queue). If Redis is lost, rebuild the sorted set by replaying DB scores with ZADD.
Periodically snapshot the full leaderboard to an RDBMS or columnar store (e.g., BigQuery, Redshift) for historical analysis — weekly top-100, month-over-month trends, cohort comparisons. These queries run offline and don’t touch Redis.
See also: Meta Interview Guide 2026: Facebook, Instagram, WhatsApp Engineering
See also: Scale AI Interview Guide 2026: Data Infrastructure, RLHF Pipelines, and ML Engineering
See also: Coinbase Interview Guide