R
Reqflow
Rebuild

Requirements & API: Real-time Gaming Leaderboard

The first move in any interview: define requirements and sketch the API before drawing a single box.

Functional requirements

  • Add or increment a player's score when they win, and reflect the new ranking immediately.
  • Return the top N players (e.g. top 10) with their scores.
  • Return a specific player's current rank.
  • Return the players ranked just above and below a given player (e.g. ±4 around them).

Non-functional requirements

  • Near real-time: a score change shows up in the ranking within a second.
  • Rank and top-N queries stay fast (sub-millisecond Redis ops) even with tens of millions of players.
  • Durable: scores survive a cache node failure (Redis is not the source of truth).
  • Scalable to 25M+ monthly players and bursty update traffic during peak hours.

API contract

POST /v1/scores { user_id, points } → { rank }
Server-validated. Maps to ZINCRBY on the sorted set.
GET /v1/leaderboard?top=10 → [{ user_id, score, rank }]
ZREVRANGE WITHSCORES, then hydrate names from MySQL.
GET /v1/scores/{user_id} → { score, rank }
ZSCORE + ZREVRANK; rank is returned directly, no scan.
GET /v1/leaderboard/around/{user_id}?range=4 → [...]
ZREVRANK to find position, then ZREVRANGE over [rank-4, rank+4].

About Real-time Gaming Leaderboard

Open any competitive game and you'll see a leaderboard: the top players, your own rank, and the handful of people just above and below you. It feels trivial until you do the math. With 25 million monthly players, computing 'what rank am I?' by sorting everyone on every request would melt a SQL database. The clean answer is a data structure most people never use directly: the Redis sorted set, which keeps millions of members ordered by score and answers rank queries in logarithmic time.

Here is the whole thing in plain terms. When a player wins a match, the game service validates the win (so nobody can fake a score from the client) and tells the leaderboard service to add a point. The leaderboard service calls ZINCRBY on a Redis sorted set, which bumps that player's score and instantly re-positions them in the ordering. To show the top 10, it calls ZREVRANGE for the highest scores. To show a single player's rank, it calls ZREVRANK, which returns their position directly without scanning. To show the four players above and below someone, it reads their rank and then grabs that small window around it.

The one idea worth getting straight is why a sorted set beats SELECT ... ORDER BY score LIMIT 10. A relational query has to sort (or at least scan an index over) a huge, constantly-changing table on every read. A sorted set keeps the members ordered as they change, so an insert, a score bump, or a rank lookup is all O(log n). Internally it's a skip list plus a hash map: the skip list keeps things ordered, the hash map jumps straight to any member.

Redis is fast but in-memory, so it isn't the source of truth. Each point is also written to MySQL, which stores the durable point history and player profiles (names, avatars). If the Redis node is lost, the leaderboard is rebuilt by replaying points from MySQL. And when one Redis node can't hold every player, you shard, either by fixed score ranges or by hashing across a Redis cluster and doing a scatter-gather to merge the top results. This system teaches sorted sets as the right tool for ranking, the cache-versus-source-of-truth split, and why rank queries are the part that doesn't scale on a plain database.