•CASE STUDY

Gaming Leaderboard (Top K)

17 min read·3,227 words·Beginner

Asked at

4 candidate reports between Oct 2025 and Jun 2026

How to use this case study

SDE-2 / Mid

  • Focus on sections 1-3: requirements, API design, and high-level architecture
  • Understand why Redis Sorted Sets are the standard choice for real-time leaderboards

SDE-3 / Senior

  • Be ready to discuss the trade-offs between Redis Sorted Sets vs a custom skip-list implementation
  • Know how to handle multiple leaderboard types (global, regional, daily) and how to distribute rewards atomically

Staff / Principal

  • Be prepared to discuss the data partitioning strategy for 100M+ players, how to handle score update storms after popular matches, and the consistency model for "top K" reads vs score writes
  • Discuss historical leaderboard archival

Problem RestatementProblem

Design a real-time leaderboard system for a massively multiplayer online game that displays the top K players (e.g., top 100) based on scores. Core challenges include handling millions of score updates per second, maintaining sorted rankings efficiently, supporting multiple leaderboard types (global, regional, daily, weekly), and serving read requests with low latency (< 100ms).

RequirementsRequirements

1.1 Functional

  • Update scores: Players earn points, update their scores in real-time.
  • Top K leaderboard: Display top 100 players globally.
  • Player rank: Show a specific player's current rank.
  • Neighboring ranks: Show players ranked around a specific player (e.g., rank 95-105 for player at rank 100).
  • Multiple leaderboards: Support global, regional (by country), and time-based (daily, weekly, monthly).
  • Historical leaderboards: View past leaderboards (e.g., last month's top 100).
  • Rewards: Distribute rewards to top K players at end of period.

1.2 Non-Functional

  • Low Latency:
  • Read (get top K): < 100ms.
  • Write (update score): < 50ms.
  • Scalability: Handle 100M active players, 1M score updates/sec.
  • Accuracy: Rankings must be correct (strong consistency for writes, eventual consistency for reads acceptable).
  • Availability: 99.9% uptime.
  • Real-Time: Leaderboard updates within 1-2 seconds of score change.

1.3 Scale Estimates

Active players

100 million

Concurrent players

10 million at peak

Leaderboard reads

10M reads/sec (players check rankings)

Total leaderboards

1 + 200 + (3 × 201) = 804 leaderboards

  • Score updates: 1M updates/sec (players complete matches, earn points).
  • Leaderboard types:
  • 1 global.
  • 200 regional (countries).
  • 3 time-based per type (daily, weekly, monthly).

1.4 API Design

The core APIs required for the service:

POST/v1/scoreUpdate ScoreUpdate player score.
GET/v1/leaderboard/:id/topGet Top KGet top K players.
GET/v1/leaderboard/:id/rank/:player_idGet RankGet player rank.
GET/v1/leaderboard/:id/neighbors/:player_idGet NeighborsGet surrounding ranks.

High-Level ArchitectureArchitecture

2.1 Overview

  • Write Path: Score Update → Score Service → Update Leaderboard (Sorted Set) → Cache.
  • Read Path: Client → API Gateway → Leaderboard Service → Cache/DB → Return Top K.
  • Key components: Redis Sorted Sets for rankings, sharding for scale, caching for reads.

2.2 Architecture Diagram

Architecture diagram
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart TB
    %% Game Clients
    Player["Player<br/>(Game Client)"] -->|"1. Score Update"| AG["API Gateway"]
    Player -->|"6. Get Top 100"| AG
    
    %% Score Update Flow
    AG -->|"2. POST /score"| ScoreService["Score Service"]
    ScoreService -->|"3. Validate & Dedupe"| Redis1["Redis<br/>(Idempotency)"]
    ScoreService -->|"4. ZINCRBY delta"| LeaderboardDB["Leaderboard DB<br/>(Redis Sorted Set)"]
    
    %% Update Multiple Leaderboards
    LeaderboardDB -->|"5a. Global"| GlobalLB["Global Sorted Set"]
    LeaderboardDB -->|"5b. Regional"| RegionalLB["Regional Sorted Set<br/>(country-based)"]
    LeaderboardDB -->|"5c. Time-Based"| TimeLB["Time-Based Sorted Set<br/>(daily/weekly/monthly)"]
    
    %% Read Flow (Top K)
    AG -->|"7. GET /leaderboard/top100"| LBService["Leaderboard Service"]
    LBService -->|"8. Check Cache"| ReadCache["Read Cache<br/>(Redis)"]
    ReadCache -->|"9a. Cache Hit"| Player
    ReadCache -->|"9b. Cache Miss"| Query["Query Sorted Set"]
    Query -->|"10. ZREVRANGE 0 99"| LeaderboardDB
    LeaderboardDB -->|"11. Top 100"| LBService
    LBService -->|"12. Cache Result"| ReadCache
    LBService -->|"13. Return Top 100"| Player
    
    %% Player Rank Query
    AG -->|"R1. GET /rank/{player_id}"| LBService
    LBService -->|"R2. ZREVRANK"| LeaderboardDB
    LeaderboardDB -->|"R3. Rank"| Player
    
    %% Analytics
    ScoreService -->|"A1. Log Event"| Analytics["Analytics Service"]
    Analytics -->|"A2. Store Events"| AnalyticsDB[(Analytics DB)]
    
    %% Archival
    TimeLB -->|"E1. End of Period"| Archival["Archival Service"]
    Archival -->|"E2. Snapshot"| ArchiveDB[(Archive DB)]
    Archival -->|"E3. Rewards"| Rewards["Reward Service"]
    
    %% Styling
    classDef client fill:#e3f2fd,stroke:#1976d2,stroke-width:2px;
    classDef service fill:#c8e6c9,stroke:#388e3c,stroke-width:2px;
    classDef storage fill:#fff9c4,stroke:#f57f17,stroke-width:2px;
    classDef lb fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px;
    
    class Player client;
    class AG,ScoreService,LBService,Analytics,Archival,Rewards service;
    class Redis1,ReadCache,AnalyticsDB,ArchiveDB storage;
    class LeaderboardDB,GlobalLB,RegionalLB,TimeLB lb;

Components (what & why)

Game Client

  • Send score updates when player earns points.
  • Request leaderboard data (top K, player rank).
  • Display rankings in game UI.

API Gateway

  • Route requests to appropriate services.
  • Rate limiting (prevent abuse).
  • Authentication.

Score Service

  • Responsibilities:
  • Validate score updates (anti-cheat).
  • Deduplicate updates (idempotency).
  • Update all relevant leaderboards (global, regional, time-based).
  • Anti-Cheat: Verify score changes are legitimate (not too large).

Leaderboard DB (Redis Sorted Sets)

  • Structure: Sorted set with player_id as member, score as value.
  • Operations:
  • ZINCRBY leaderboard delta player_id: Atomically add points to a player's score.
  • ZADD leaderboard score player_id: Set an absolute score (e.g. when rebuilding from the DB).
  • ZREVRANGE leaderboard 0 99: Get top 100 players (descending).
  • ZREVRANK leaderboard player_id: Get player's rank.
  • ZSCORE leaderboard player_id: Get player's score.
  • Why Redis: In-memory, O(log n) operations, atomic updates.

Read Cache (Redis)

  • Cache top K leaderboard results.
  • TTL: 1-5 seconds (balance freshness vs load).
  • Cache Key: leaderboard:global:top100.

Leaderboard Service

  • Responsibilities:
  • Fetch top K from cache or DB.
  • Fetch player rank.
  • Fetch neighboring ranks.
  • Optimization: Batch queries, parallel fetches.

Analytics Service

  • Log all score updates for analytics.
  • Detect anomalies (cheating, bugs).

Archival Service

  • Snapshot leaderboards at end of period (daily, weekly, monthly).
  • Store in Archive DB for historical queries.
  • Trigger reward distribution.

Reward Service

  • Calculate rewards for top K players.
  • Distribute in-game currency, items, badges.

Data ModelData model

Leaderboard (Redis Sorted Set)

leaderboard:global -> {
  player_123: 95000,
  player_456: 89000,
  player_789: 87500,
  ...
}

Player

Player(
  player_id,
  username,
  country,
  total_score,
  created_at
)

Score Event (Analytics)

ScoreEvent(
  event_id,
  player_id,
  score_delta,
  new_score,
  timestamp,
  match_id
)

Archived Leaderboard

ArchivedLeaderboard(
  leaderboard_id,
  type,  -- GLOBAL, REGIONAL, DAILY, WEEKLY, MONTHLY
  region,
  start_date,
  end_date,
  top_k_snapshot  -- JSON: [{player_id, score, rank}, ...]
)

Key FlowsFlows

5.1 Update Score Flow

  1. Player completes match, earns 500 points.
  2. Game client calls POST /score {player_id: 123, score_delta: 500, match_id}.
  3. Score Service validates update (anti-cheat check).
  4. Score Service checks idempotency (skip if already processed).
  5. Score Service updates leaderboards:
  • Global: ZINCRBY leaderboard:global 500 player_123.
  • Regional: ZINCRBY leaderboard:us 500 player_123.
  • Daily: ZINCRBY leaderboard:daily:2023-10-20 500 player_123.
  • All three in one MULTI/pipeline. ZINCRBY adds the delta atomically on the server; a read-then-ZADD would lose points when two matches finish at the same time.
6. Read caches are not invalidated; they expire within 2–5 seconds (see Deep Dive C).

  1. Analytics Service logs event.

5.2 Get Top K Flow

  1. Player opens leaderboard screen, requests top 100.
  2. Client calls GET /leaderboard/global/top100.
  3. Leaderboard Service checks Read Cache.
  4. Cache hit: Return cached result.
  5. Cache miss:
  • Query: ZREVRANGE leaderboard:global 0 99 WITHSCORES.
  • Redis returns top 100 players with scores.
  • Fetch player usernames from Player DB (batch query).
  • Cache result with 2-second TTL.
6. Return to client.

5.3 Get Player Rank Flow

  1. Player wants to know their current rank.
  2. Client calls GET /rank/{player_id}.
  3. Leaderboard Service queries: ZREVRANK leaderboard:global player_123.
  4. Redis returns rank (e.g., 1245).
  5. Return to client: "You are ranked #1245".

5.4 Get Neighboring Ranks Flow

  1. Player at rank 1245 wants to see ranks 1240-1250.
  2. Client calls GET /leaderboard/global/neighbors/{player_id}.
  3. Leaderboard Service:
  • Get player rank: ZREVRANK leaderboard:global player_123 → 1245.
  • Fetch range: ZREVRANGE leaderboard:global 1240 1250 WITHSCORES.
4. Return players ranked 1240-1250.

5.5 End-of-Period Flow (Daily Leaderboard)

  1. Cron job triggers at midnight (end of day).
  2. Archival Service:
  • Fetches entire daily leaderboard: ZREVRANGE leaderboard:daily:2023-10-20 0 -1 WITHSCORES.
  • Saves snapshot to Archive DB.
  • Distributes rewards to top 100 players.
3. Deletes old daily leaderboard: DEL leaderboard:daily:2023-10-20.

  1. Creates new leaderboard for next day.

Deep Dive A: Efficient Data Structures for Top K (~10 mins)Deep dive

Problem

Store and rank 100M players efficiently. Need O(log n) updates and O(K) Top K queries.

Solution: Redis Sorted Set

Why Redis Sorted Set?

  • In-Memory: Ultra-fast reads/writes.
  • Sorted: Automatically maintains sorted order by score.
  • Atomic Operations: ZINCRBY, ZADD, ZREVRANGE are atomic.
  • Fits in memory: 100M members × ~100 bytes (member + score + skiplist/hash overhead) ≈ 10 GB — one node can hold it. What one node can't do is 1M updates/sec across several leaderboards (see Deep Dive B).
  • Logarithmic Complexity: O(log n) for insert/update.

Key Operations

# Add points to a player's score (atomic read-modify-write)
ZINCRBY leaderboard:global 500 player_123

# Set an absolute score
ZADD leaderboard:global 95000 player_123

# Get top 100 (descending by score)
ZREVRANGE leaderboard:global 0 99 WITHSCORES

# Get player's rank (0-indexed)
ZREVRANK leaderboard:global player_123

# Get player's score
ZSCORE leaderboard:global player_123

# Get count of players
ZCARD leaderboard:global

Time Complexity

  • Insert/Update: O(log n).
  • Get Top K: O(log n + K).
  • Get Rank: O(log n).
  • Get Score: O(1).

Tie-Breaking

Two players with 95,000 points get an arbitrary order (Redis falls back to comparing member names). The usual fix is to fold the tie-breaker into the score: whoever reached the score first ranks higher.

score = points × 10^10 + (MAX_TS − timestamp_of_last_score_change)

A sorted-set score is a 64-bit float with 53 bits of exact integer precision, so budget the bits: e.g. points up to ~900K with a seconds-precision timestamp offset. Display floor(score / 10^10) as the points.

Alternative: Min Heap (In-Memory)

Approach

  • Maintain min heap of size K (top K players).
  • When new score comes:
  • If score > heap min: Replace min with new score, heapify.
  • Else: Ignore.

Pros

  • Space Efficient: Only store K items (vs all players).
  • Fast Reads: O(1) to get top K.

Cons

  • Slow Writes: O(n) to find player for update.
  • No Full Ranking: Can't get rank for players outside top K.

When to Use

  • Only need top K (no full ranking).
  • K is small (e.g., 100).

Hybrid Approach (Sharding + Sorted Set)

Problem

Memory is not the constraint (~10 GB for 100M players). Throughput is: each score update touches ~3 leaderboards (global, regional, daily), so 1M updates/sec is ~3M sorted-set writes/sec — more than one Redis node (~100K–200K ops/sec for these commands) can absorb.

Solution

  • Shard by Score Range:
  • Shard 1: Scores 0-1M.
  • Shard 2: Scores 1M-2M.
  • Shard 3: Scores 2M+.
  • Top K Query: Query top K from each shard, merge results.

Data Structure Comparison

Architecture diagram
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart TD
    Problem["Top K Problem<br/>(100M players)"]
    
    Problem --> RS["Redis Sorted Set"]
    Problem --> Heap["Min Heap"]
    Problem --> Shard["Sharded Sorted Set"]
    
    RS --> RSPros["✅ O(log n) ops<br/>✅ Full ranking<br/>✅ Atomic"]
    RS --> RSCons["❌ Single-node write throughput<br/>❌ All data in RAM"]
    
    Heap --> HeapPros["✅ Space efficient<br/>✅ O(1) top K read"]
    Heap --> HeapCons["❌ No full ranking<br/>❌ Slow updates"]
    
    Shard --> ShardPros["✅ Horizontal scale<br/>✅ Full ranking"]
    Shard --> ShardCons["❌ Complex merging<br/>❌ Cross-shard queries"]
    
    classDef pros fill:#c8e6c9,stroke:#388e3c,stroke-width:2px;
    classDef cons fill:#ffcdd2,stroke:#c62828,stroke-width:2px;
    
    class RSPros,HeapPros,ShardPros pros;
    class RSCons,HeapCons,ShardCons cons;

Deep Dive B: Sharding Strategies (~10 mins)Deep dive

Problem

100M players fit in one Redis node's memory (~10 GB), but ~3M sorted-set writes/sec (1M updates × 3 leaderboards) and 10M reads/sec do not fit in one node's throughput. We need to spread writes — and still answer "top K" and "what is my rank?" globally.

Sharding Strategies

Strategy 1: Shard by Player ID (Hash-Based)

player_123 -> Shard 3 (hash(player_123) % 10 = 3)
player_456 -> Shard 6 (hash(player_456) % 10 = 6)
Top K Query
  • Query top K from each shard.
  • Merge results globally.
  • Example: Top 100 global.
  • Fetch top 100 from each of 10 shards.
  • Merge 1000 results, select top 100.

Pros:
  • Even distribution of players.
  • Parallel queries.

Global Rank
ZREVRANK on a player's own shard is only their rank *within that shard*. Global rank = number of players with a higher score on every shard:
rank(player) = Σ over shards  ZCOUNT(leaderboard, "(" + score, "+inf")

That is one O(log n) call per shard, sent in parallel — fine for 10–20 shards. At larger scale, keep a per-shard histogram of score buckets and return an approximate rank ("top 3%") for players outside the top ~10K, which is what most games show anyway.

Cons:
  • Must query all shards for top K and for global rank.
  • Merge overhead.

Strategy 2: Shard by Score Range

Score 0-10K    -> Shard 0
Score 10K-100K -> Shard 1
Score 100K+    -> Shard 2
Top K Query
  • Start with highest score shard (Shard 2).
  • Fetch top K from Shard 2.
  • Optimization: If Shard 2 has ≥ K players, done.

Pros:
  • Top K query very fast (only query top shards).
  • Natural sharding by performance tier.

Global Rank
  • Easy: rank = (players in all higher-score shards, via ZCARD) + ZREVRANK in the player's own shard.

Cons:
  • Uneven distribution (top shard may be hot).
  • Need to rebalance as score distribution changes.
  • A player whose score crosses a boundary must move shards (remove from one, add to another) — not atomic across nodes, so briefly visible in both or neither.

Strategy 3: Geo-Sharding (Regional Leaderboards)

US players     -> US Shard
EU players     -> EU Shard
Asia players   -> Asia Shard
Use Case:
  • Regional leaderboards (no global merge needed).

Pros:
  • Perfect for regional leaderboards.
  • Low latency (geo-distributed).

Cons:
  • Global leaderboard requires merging all regions.

  • Regional Leaderboards: One sorted set per region; most fit on a single node.
  • Global Leaderboard: Shard by player ID for even write load. Top K = merge each shard's top K. Global rank = parallel ZCOUNT across shards (exact), or score-bucket histograms (approximate) outside the top 10K.
  • Optimization: Cache global top K (refresh every 2 seconds) — it is the most-read and most expensive query.

Sharding Architecture

Architecture diagram
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart TB
    TopK["Top K Query"] --> Coord["Query Coordinator"]
    
    Coord -->|"parallel query"| S1["Shard 1<br/>(players 1-10M)"]
    Coord -->|"parallel query"| S2["Shard 2<br/>(players 10M-20M)"]
    Coord -->|"parallel query"| S10["Shard 10<br/>(players 90M-100M)"]
    
    S1 -->|"top 100"| Merge["Merge Results"]
    S2 -->|"top 100"| Merge
    S10 -->|"top 100"| Merge
    
    Merge -->|"global top 100"| Cache["Cache Result<br/>(2s TTL)"]
    Cache --> TopK
    
    classDef shard fill:#fff9c4,stroke:#f57f17,stroke-width:2px;
    classDef merge fill:#c8e6c9,stroke:#388e3c,stroke-width:2px;
    
    class S1,S2,S10 shard;
    class Coord,Merge,Cache merge;

Deep Dive C: Real-Time Updates & Caching (~8 mins)Deep dive

Problem

1M score updates/sec, 10M leaderboard reads/sec. Need low latency without overwhelming DB.

Write Optimization

Batching

  • Accumulate score updates for 1-2 seconds.
  • Batch update Redis in single operation.
  • Trade-off: Slight delay (1-2s) acceptable for leaderboard.

Idempotency

  • Use idempotency key (match_id + player_id).
  • Store in Redis with TTL (5 minutes).
  • Check: Before applying update, check if already processed.

Read Optimization

Multi-Layer Caching

L1: Application Cache (In-Memory)
  • Cache top K in application memory (each API server).
  • TTL: 2 seconds.
  • Benefit: Serve reads without Redis query.

L2: Redis Cache
  • Cache top K results in Redis.
  • TTL: 5 seconds.
  • Key: cache:leaderboard:global:top100.

L3: Redis Sorted Set (Source of Truth)
  • Authoritative leaderboard data.

Cache Invalidation Strategy

Time-Based (TTL)
  • Cache expires after TTL (e.g., 2 seconds).
  • Next read refreshes cache.
  • Pros: Simple, works well for high-read scenarios.

Write-Through
  • On score update, invalidate cache immediately.
  • Cons: High write rate → constant cache invalidation.

  • Use TTL (2-5 seconds).
  • Accept slightly stale data (eventual consistency).

Read/Write Flow Optimization

# Write (Score Update)
def update_score(player_id, match_id, score_delta):
    # Idempotency: the same match must not be counted twice (SET NX = only first caller wins)
    if not redis.set(f"scored:{match_id}:{player_id}", 1, nx=True, ex=300):
        return
    
    # ZINCRBY adds the delta atomically, so concurrent updates never lose points
    pipe = redis.pipeline(transaction=True)
    pipe.zincrby("leaderboard:global", score_delta, player_id)
    pipe.zincrby(f"leaderboard:{region}", score_delta, player_id)
    pipe.zincrby(f"leaderboard:daily:{date}", score_delta, player_id)
    pipe.execute()
    
    # No cache invalidation (TTL handles it)

# Read (Top K)
def get_top_k(k=100):
    # Check L1 cache (app memory)
    if app_cache.has("top100"):
        return app_cache.get("top100")
    
    # Check L2 cache (Redis)
    if redis.exists("cache:top100"):
        result = redis.get("cache:top100")
        app_cache.set("top100", result, ttl=2)
        return result
    
    # Query source (Redis Sorted Set)
    result = redis.zrevrange("leaderboard:global", 0, 99, withscores=True)
    
    # Cache result
    redis.setex("cache:top100", 5, result)
    app_cache.set("top100", result, ttl=2)
    
    return result

Caching Architecture

Architecture diagram
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
    Read["Read Request"] --> L1["L1: App Cache<br/>(2s TTL)"]
    L1 -->|"miss"| L2["L2: Redis Cache<br/>(5s TTL)"]
    L2 -->|"miss"| L3["L3: Redis Sorted Set<br/>(Source of Truth)"]
    
    L1 -->|"hit"| Return["Return Result<br/>(<0.1ms, no network)"]
    L2 -->|"hit"| Return2["Return Result<br/>(~1ms)"]
    L3 -->|"query"| Return3["Return Result<br/>(~1-2ms per shard + merge)"]
    
    classDef cache fill:#c8e6c9,stroke:#388e3c,stroke-width:2px;
    
    class L1,L2,L3 cache;

Scaling & Performance (~5 mins)Scale

Horizontal Scaling

  • Score Service: Stateless, scale with load balancer.
  • Leaderboard Service: Stateless, scale horizontally.
  • Redis: Shard across multiple instances.

Performance Metrics

  • Write latency: ~1–5ms end to end (a Redis ZINCRBY itself is well under 1ms; the rest is network and the idempotency check).
  • Read latency: <1ms from app cache, ~1–2ms from Redis, a few ms for a cache-miss top-K merge across shards.
  • Throughput: 1M writes/sec, 10M reads/sec.

Bottlenecks & Mitigations

  • Redis Write Throughput: Shard by player ID; batch updates per shard with pipelining.
  • Read Load: Multi-layer caching (app cache + Redis cache).
  • Write Load: Batch updates, async processing.

Failure Modes & Recovery

Redis Failure

  • Impact: Leaderboard unavailable.
  • Mitigation: Redis Cluster with replication (3× replicas).

Cache Invalidation Storm

  • Impact: Cache expires for popular leaderboard → all requests hit DB.
  • Mitigation: Probabilistic early expiration (jitter in TTL).

Score Update Loss

  • Impact: Player score not updated.
  • Mitigation: Log all updates to Kafka, replay on failure.

Trade-offs & AlternativesTrade-offs

Redis vs Database

  • Redis: In-memory, fast, limited durability.
  • Database: Persistent, slower, complex indexing.
  • Choice: Redis for hot data, DB for archival.

Strong vs Eventual Consistency

  • Strong: All players see same leaderboard immediately.
  • Eventual: Slight delays acceptable (1-2 seconds).
  • Choice: Eventual (caching with TTL).

Real-Time vs Batch Processing

  • Real-Time: Update leaderboard immediately.
  • Batch: Update every 5 minutes.
  • Choice: Real-time with 1-2 second caching.

Security & Anti-Cheat

Score Validation

  • Verify score changes are within expected range.
  • Flag suspicious updates (e.g., 100K points in 1 second).

Rate Limiting

  • Limit score updates per player (e.g., max 10/minute).

Audit Logs

  • Log all score updates for forensic analysis.

Interview Time Allocation (45 min)

  • 5 min: Requirements & scope (functional, non-functional, scale).
  • 10 min: HLD & architecture diagram (write + read paths).
  • 5 min: Data model & key flows (update, top K, rank).
  • 10 min: Deep dive on efficient data structures (Redis Sorted Set vs Min Heap).
  • 10 min: Deep dive on sharding strategies (player ID, score range, geo).
  • 5 min: Real-time updates, caching, scaling, failure handling.

SummaryWrap-up

  • Core Challenges: Efficiently rank 100M players, handle 1M writes/sec and 10M reads/sec, real-time updates with low latency.
  • Key Components:
  • Redis Sorted Set: O(log n) updates, O(K) Top K queries.
  • Sharding: Partition by player ID, merge results for global Top K.
  • Multi-Layer Caching: App cache (2s) + Redis cache (5s) + Sorted Set.
  • Idempotency: Prevent duplicate updates.
  • Scaling Strategy: Shard Redis, cache aggressively, batch updates.
  • Performance: ~1–5ms writes, ~1ms cached reads, 1M writes/sec, 10M reads/sec.

This design supports real-time leaderboards for massive multiplayer games with millions of players and billions of score updates daily.

More Case Studies

Frequently Asked Questions

What is the Gaming Leaderboard (Top K) system design question?

Gaming Leaderboard (Top K) is a system design interview question asked at FAANG companies. It covers distributed systems,real-time,storage and tests your ability to design scalable, production-ready systems. InterviewSkool's breakdown walks you through requirements, API design, architecture, and trade-offs.

Which companies ask the Gaming Leaderboard (Top K) question?

Meta, Uber have reportedly asked variations of this question in system design interviews. The exact wording may differ, but the core design challenges remain the same.

How should I prepare for the Gaming Leaderboard (Top K) interview question?

Start with the problem statement and scale estimates, then design the high-level architecture. Focus on the core components, data model, and API design. InterviewSkool's breakdown covers the full solution with mermaid diagrams and trade-off analysis to help you prep efficiently.

What level is the Gaming Leaderboard (Top K) question?

This question is suitable for SDE-2, SDE-3, and Staff engineer interviews. The level guidance on this page provides specific tips for each level — SDE-2 candidates should focus on core architecture, while Staff engineers should discuss trade-offs, monitoring, and incremental rollouts.

Practice with a Mock Interview

Apply what you learned in a live system design mock interview with InterviewSkool's AI interviewer.

Start System Design Interview →