Gaming Leaderboard System Low-Level Design

Requirements

A gaming leaderboard system must support:

  • Update a player’s score in real-time (on match completion)
  • Query a player’s current rank in O(log n)
  • Retrieve top-K players with their scores and ranks
  • Friend leaderboard – rank a player among their friends only
  • Time-bucketed leaderboards – weekly, monthly, and all-time
  • Support tens of millions of players and millions of score updates per day

Redis Sorted Set as the Core Data Structure

Redis sorted sets (ZSET) are the ideal primitive for leaderboards. Internally implemented as a skip list + hash map, they provide:

  • ZADD leaderboard {score} {player_id} – insert or update score – O(log n)
  • ZREVRANK leaderboard {player_id} – 0-based rank from highest score – O(log n)
  • ZRANK leaderboard {player_id} – rank from lowest score – O(log n)
  • ZREVRANGE leaderboard 0 9 WITHSCORES – top 10 players – O(log n + k)
  • ZRANGEBYSCORE – players within a score range – O(log n + k)
  • ZMSCORE leaderboard player1 player2 ...) – batch score lookup – O(k)

A single Redis sorted set can hold 100M+ members and all operations remain fast due to the skip list structure.

Global Leaderboard

Implementing the global all-time leaderboard is straightforward with Redis:

  • On score update: ZADD leaderboard:global {new_score} {player_id}
  • Get player rank: ZREVRANK leaderboard:global {player_id} (add 1 for 1-based rank)
  • Get top 10: ZREVRANGE leaderboard:global 0 9 WITHSCORES
  • Get player score: ZSCORE leaderboard:global {player_id}

Rank is 0-indexed in Redis – add 1 when displaying to users.

Score Update Strategies

Two common semantics for updating scores:

  • High score (best performance): Only update if the new score is higher than the existing score. Use ZADD leaderboard GT {new_score} {player_id}. The GT flag updates only if new score is greater than the current score.
  • Cumulative score (total points): Add to the existing score. Use ZINCRBY leaderboard {delta} {player_id}. Common for “total XP earned” style leaderboards.

Pick the right semantic for your game type. A battle royale leaderboard typically uses high score; an RPG progression leaderboard uses cumulative.

Friend Leaderboard

A friend leaderboard ranks a player among their friends – typically a few hundred people at most.

Implementation:

  • Store the friend graph in a relational DB or Redis set: SMEMBERS friends:{player_id}
  • On query: fetch the friend list (typically < 500 members)
  • Batch-fetch all friends’ scores: ZMSCORE leaderboard:global friend1 friend2 ...
  • Sort client-side (or in app logic) and find the player’s position

This is O(friends) which is fast because friend counts are bounded. No need to maintain a separate sorted set per player. The tradeoff: slightly more latency (two round trips to Redis) vs. simplicity.

For very social games (>5000 friends), maintain a per-player friend leaderboard sorted set and update it on every score change of any friend – but this is rarely necessary.

Time-Bucketed Leaderboards

Weekly and monthly leaderboards require separate sorted sets per time bucket:

  • Weekly key: leaderboard:week:{year}:{week_number} – e.g., leaderboard:week:2024:42
  • Monthly key: leaderboard:month:{year}:{month} – e.g., leaderboard:month:2024:10
  • All-time key: leaderboard:global

On each score update, update all relevant buckets atomically using a Redis pipeline:

  • ZINCRBY leaderboard:global {delta} {player_id}
  • ZINCRBY leaderboard:month:2024:10 {delta} {player_id}
  • ZINCRBY leaderboard:week:2024:42 {delta} {player_id}

Set TTLs to control memory usage:

  • Weekly keys: TTL of 90 days (keep ~12 weeks of history)
  • Monthly keys: TTL of 2 years
  • All-time: no TTL

Sharding for Scale

A single Redis sorted set can hold tens of millions of members before memory becomes a concern. If you need to scale beyond a single node:

  • Shard by player_id % N: Each shard holds 1/N of the players.
  • Score updates: Route to the correct shard based on player_id.
  • Global top-K: Query top-K from each of the N shards, then merge the N sorted lists to find the true global top-K. This is O(N * K) per global top-K query – fine for small N and K=100.
  • Global rank: To find the exact rank of a player, query each shard for “how many players scored more than X?” and sum the counts. This is O(N) Redis calls but can be pipelined.

For most games with under 50M players, a single Redis instance is sufficient. Sharding adds complexity – only introduce it when needed.

Persistence

Redis is an in-memory store. Plan for durability:

  • Redis AOF (Append-Only File): Every write is logged to disk. Low data loss risk but higher disk I/O.
  • Redis RDB snapshots: Periodic full snapshots. Simple but can lose up to snapshot_interval seconds of data on crash.
  • DB sync: Periodically (e.g., every 5 minutes) snapshot the top-N players and full score data into a relational DB. This provides a recovery point and enables historical queries that Redis TTL would expire.

For leaderboards, losing a few seconds of score updates is usually acceptable. Use RDB with a 60-second interval as a pragmatic default.

Scale Numbers

Reference numbers for capacity planning:

  • Redis sorted set: 100M+ members per set, ~100 bytes per member including overhead
  • 100M players = ~10 GB of memory – fits on a single high-memory Redis instance
  • ZRANK / ZADD: O(log n) – sub-millisecond for 100M members
  • Throughput: 1M+ score update operations per second feasible with pipeline batching across multiple connections
  • Top-10 query: sub-millisecond always (O(log n + 10))

Anti-Cheat Hooks

Leaderboards are high-value targets for cheating. Basic defenses:

  • Rate limiting: Enforce a maximum number of score update events per player per second. A legitimate player cannot complete more than N matches per hour – anything above is suspicious.
  • Score delta validation: Validate that the score delta for a single match falls within the range of what is achievable in the game. A player reporting +1,000,000 points from a single match where the max is 10,000 is clearly invalid.
  • Server-authoritative scoring: Game servers – not clients – submit scores. Never trust score values that originate from the client.
  • Statistical anomaly detection: Flag players whose score velocity (points per hour) is more than 3 standard deviations above the population mean for human review.
{“@context”:”https://schema.org”,”@type”:”FAQPage”,”mainEntity”:[{“@type”:”Question”,”name”:”How do you implement a real-time leaderboard with Redis?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Use a Redis sorted set: ZADD leaderboard {score} {player_id} to add or update a player's score. The sorted set maintains score order automatically. Fetch top-K: ZREVRANGE leaderboard 0 K-1 WITHSCORES (O(log n + k)). Get a player's rank: ZREVRANK leaderboard player_id (O(log n), 0-indexed so rank = result + 1). Update score for "high score only" (never decrease): use ZADD leaderboard NX {score} {player_id} – NX means only add if not exists. Then ZADD leaderboard GT {score} {player_id} – GT means only update if new score is greater. For cumulative scores: ZINCRBY leaderboard {delta} {player_id}.”}},{“@type”:”Question”,”name”:”How do you implement a friend leaderboard?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”A friend leaderboard shows rankings among a player's friends. Approach: (1) Maintain the global leaderboard sorted set. (2) On query for player P: fetch P's friend list (from a social graph table or Redis set friends:{player_id}). (3) Batch-fetch scores of all friends using ZMSCORE leaderboard friend1 friend2 … (O(k) where k = number of friends). (4) Sort results client-side and return the ranked list. This is O(k log k) on the client for small friend lists (< 10K). Do not create per-player friend leaderboard sorted sets – they would require O(followers) updates on every score change and would not scale for popular players. The batch ZMSCORE approach is O(1) per score update and O(k) per query.”}},{“@type”:”Question”,”name”:”How do you implement weekly and all-time leaderboards simultaneously?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Use separate Redis sorted sets with time-based keys. All-time: key = "leaderboard:alltime". Weekly: key = "leaderboard:week:{year}:{week_number}". Monthly: key = "leaderboard:month:{year}:{month}". On every score update, update all relevant buckets atomically using a Redis pipeline: ZINCRBY or ZADD on each key in a single batch. Set TTL on time-bounded keys: weekly keys expire after 90 days (EXPIRE leaderboard:week:{y}:{w} 7776000). Monthly keys expire after 2 years. When a weekly period ends, the key continues to exist until its TTL expires, preserving the historical leaderboard for that week. Avoid scanning old keys – always use explicit time-based key names rather than patterns.”}},{“@type”:”Question”,”name”:”How do you scale a leaderboard to 100M+ players?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Redis sorted set handles 100M+ members natively – ZRANK is O(log n) even at this scale. For single-server Redis: 100M entries * ~200 bytes per entry = 20GB of RAM, which fits on a high-memory instance. If RAM is the bottleneck: (1) Shard by player_id % N: maintain N separate sorted sets, each with 100M/N players. Global rank = approximate (requires merging top-K from each shard). (2) Approximate rank with quantile sketching: maintain the global distribution, report approximate rank in O(1). For exact global rank with sharding: on each score update, record the exact score in the DB. On rank query: count the number of players with a higher score using a DB index (SELECT COUNT(*) WHERE score > X), or maintain a histogram and estimate rank from the score distribution.”}},{“@type”:”Question”,”name”:”How do you prevent score manipulation and cheating in a leaderboard?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Four layers: (1) Rate limiting: a player can submit at most N score updates per second (based on game mechanics). Enforce with Redis counters: INCR score_update_rate:{player_id} with 1-second TTL. If rate exceeds limit, reject. (2) Score delta validation: the maximum score increase per update depends on game mechanics. If a player claims 10M points in a single action that can award at most 1000 points, reject the update. (3) Anomaly detection: flag sudden score jumps (player goes from rank 100K to rank 1 in one update). Queue for manual review. (4) Server-side score calculation: for competitive games, never trust the client's reported score. Calculate score server-side from game events. Client sends events (killed enemy, collected item), server tallies the score. Harder to cheat since the client cannot report arbitrary scores.”}}]}

Twitch has real-time leaderboards for gaming competitions. See system design patterns for Twitch interview: gaming leaderboard and real-time ranking.

Uber uses ranking systems for driver ratings and incentives. See design patterns for Uber interview: real-time ranking and leaderboard design.

Snowflake interviews cover real-time ranking and analytics. See design patterns for Snowflake interview: leaderboard and ranking system design.

See also: Meta Interview Guide 2026: Facebook, Instagram, WhatsApp Engineering

See also: Netflix Interview Guide 2026: Streaming Architecture, Recommendation Systems, and Engineering Excellence

See also: Snap Interview Guide

Scroll to Top