Design a Real-Time Gaming Leaderboard
Fast recall
Quick revision
Core answer
ZADD updates a score atomically; ZRANK and ZREVRANGE give rank/range in O(log N). Layer the design as: Game Service publishes score events to Kafka; a Score Aggregator writes to per-leaderboard ZSETs in Redis (global, regional, friend-group, time-windowed). Cache the top-N in CDN/edge for the public view; serve rank-of-self + nearby slice from Redis for personalized views. Persist scores to a durable store (Postgres/Cassandra) for replay and recovery, and rebuild Redis from there on cold start.Choosing the right data structure (Redis sorted sets), partitioning leaderboards by scope and time, durability vs in-memory trade-offs, and handling tie-breaking + cheating.
Relational `ORDER BY score DESC LIMIT N` for rank-of-self (O(N) count of higher scores), or treating Redis as the source of truth with no durable backstop.
Discuss partitioned leaderboards (global/regional/friends), time-windowed (daily/weekly/all-time) with separate ZSETs, anti-cheat verification before publish, and cold-start rebuild strategy.
Interview-ready guide
Interview answer
Clarifying questions I'd ask first
- Score semantics — monotonically increasing or arbitrary?
- Leaderboard scopes — global only or also regional, friends?
- Time windows — all-time, daily, weekly, seasonal?
- Tie-breaking rule?
- Anti-cheat — server-validated or client-trusted?
Assumptions for this design
10M total users, 1M DAU, 100K concurrent during tournaments. Score updates ~5K/sec average, 100K/sec at tournament peak. Boards: global all-time, daily, weekly, regional (10), friend-group on-the-fly. Tie-break by earlier-reach time. Sub-100ms p99 for write, sub-50ms for read.
Quick capacity check
- 700 MB Redis for the global ZSET; ~5 GB total across all boards
- 80K ZADD/sec per Redis instance, sharded cluster scales horizontally
- 100K writes/sec at peak handled by partitioning across boards
- Top-N read absorbed ~95% by CDN; origin sees a fraction
The core decision — Redis Sorted Sets
- Internally a skip list + hash table
- O(log N) for ZADD (update score), ZRANK (get rank), ZRANGE (get top-N or nearby slice)
- For 10M members that's ~23 operations per lookup vs 10 million for a SQL count
- Rejected SQL
ORDER BY score DESCbecause "my rank" is O(N) (count of higher scores) - Rejected in-memory app-tier rank because it doesn't survive restarts or scale across instances
Partitioning by scope and time window
- One ZSET per (board, time window):
lb:global:all-time,lb:global:daily:2026-04-21,lb:regional:us-west:all-time - Time-windowed boards roll over (daily at midnight UTC, weekly on Mondays); old keys retained with 30-day TTL
- BoardResolver computes which boards a score event affects — typically global + region + daily + weekly
- Friend-group is computed on the fly: ZMSCORE for each friend ID, sort in memory
Tie-breaking via score encoding
encoded_score = raw_score * 1e9 - (timestamp_ms - epoch)- For equal raw scores, earlier reach has higher encoded score
- Encoded value stays within 64-bit double precision
High-level shape
- Submit path: Game Service → Anti-Cheat validates → Kafka → Score Aggregator → ZADD across all relevant ZSETs + Cassandra write
- Read path top-N: CDN with 10s TTL → (on miss) Leaderboard API → ZREVRANGE on Redis
- Read path personalized: bypass CDN → Leaderboard API → ZREVRANK + ZRANGE for nearby slice
- Cassandra is the durable backstop; Redis can be rebuilt from
current_scoreson cold start
LLD — clean abstractions
LeaderboardStoreinterface (RedisLeaderboardStore impl); in-memory TreeMap impl for testsScoreAggregatoris idempotent on event_id (ZADD same value is no-op; Cassandra upsert idempotent)BoardResolverextracts membership logicScoreEncoderfor tie-break encodingCacheWarmerrebuilds Redis from Cassandra on cold start
Anti-cheat
- Score endpoints are server-to-server only — no client direct access
- Game Service validates match outcome (replay or server simulation)
- Anti-Cheat module checks implausible growth, signatures, IP/device anomalies
- Suspicious scores quarantined for manual review before adding to leaderboard
Failure handling
- Redis down: reads serve from CDN; CacheWarmer rebuilds from Cassandra on recovery
- Kafka down: submission service buffers locally; returns 503 on overflow
- Cassandra down: Redis stays current; Cassandra writes accumulate in retry topic
- Aggregator crash: Kafka replay; ZADD and Cassandra upsert are idempotent
- Tournament burst: pre-scale, lower CDN TTL on tournament boards, more Kafka partitions
Production extras
- Top-N CDN with 10s TTL (1s during tournament finals)
- JWT scopes: public top-N; auth required for own-rank; elevated role for admin score modification
- Audit log of every score modification
- GDPR delete: ZREM from all boards on user deletion
- Display names stored separately so renames don't require reindexing
At 10× scale
- Per-region Redis clusters with cross-region async merge for global
- Tiered storage — only top 1M live in Redis, deeper served from Cassandra
- Edge compute on CDN serving top-N JSON without origin hit
Recommendation
Redis Sorted Sets as the leaderboard primitive, partitioned per (scope, time window) ZSETs, Kafka decoupling submission from aggregation, Cassandra as the durable backstop with cold-start rebuild, CDN-cached top-N with 10s TTL, and server-validated anti-cheat at the submission boundary — the same shape Steam, Epic, and Riot run for ranked play.
Design a Real-Time Gaming Leaderboard
Direct Answer
Use Redis Sorted Sets as the leaderboard primitive — ZADD updates a score atomically; ZRANK and ZREVRANGE give rank/range in O(log N). Layer the design as: Game Service publishes score events to Kafka; a Score Aggregator writes to per-leaderboard ZSETs in Redis (global, regional, friend-group, time-windowed). Cache the top-N in CDN/edge for the public view; serve rank-of-self + nearby slice from Redis for personalized views. Persist scores to a durable store (Postgres/Cassandra) for replay and recovery, and rebuild Redis from there on cold start.
Common Mistakes & Pitfalls
Relational ORDER BY score DESC LIMIT N for rank-of-self (O(N) count of higher scores), or treating Redis as the source of truth with no durable backstop.
Check your understanding
Answer these without looking back at the article.
- How would you implement a friends-only leaderboard for a user with 10,000 friends without slowing down the read?
- Walk through the cold-start rebuild after a full Redis cluster restart — how long does it take and what do users see meanwhile?
- How does your design change if tie-breaking should be random (no implicit advantage to early players)?
- Design the historical-leaderboard view (last season's top 100) — what changes from the live design?
Related interview prep categories
1Want a guided curriculum order?
These modules are also sequenced in our flagship backend interview track — open it when you prefer an ordered path instead of browsing by topic.