Requirements
A gaming backend handles player authentication, matchmaking (pairing players of similar skill), real-time game state synchronization, leaderboards, and player profile storage. Each of these components has distinct technical requirements. Matchmaking is a search problem (find compatible players quickly). Real-time sync is a latency-sensitive messaging problem. Leaderboards are a ranking problem at scale. This system design is asked at gaming companies and any company with real-time competitive features. Scale: Fortnite peak concurrent players: 8.3 million. League of Legends: 150 million registered players. Even at startup scale, the real-time components are technically challenging.
Matchmaking Service
Goal: pair players into a match where all players have similar skill (ELO, MMR — Matchmaking Rating). Approach: players submit a matchmaking request (player_id, game_mode, mmr). The matchmaking service maintains a queue of waiting players per game mode. Matching algorithm: search the queue for a group of players whose MMRs are within an acceptable range of each other (e.g., ±50 MMR initially). Expanding search: if no match is found after 30 seconds, expand the acceptable range (±100 MMR after 30s, ±200 after 60s). This trades match quality for wait time. Implementation: Redis sorted set per game mode: ZADD mm:queue:5v5 {mmr} {player_id}. Find candidates: ZRANGEBYSCORE mm:queue:5v5 (target_mmr – delta) (target_mmr + delta). If 10 players found (5v5): create a match, notify all 10, remove from queue. Match confirmation: each player confirms within 15 seconds. If any player declines or times out: return the other 9 to the queue. Anti-abuse: track acceptance rate; players with < 70% acceptance rate incur longer queue delays. Regional isolation: match players in the same region (us-east, eu-west) to minimize latency. Cross-region matching only when queue is very sparse.
Real-Time Game State Synchronization
In a real-time multiplayer game, all players must see a consistent view of the game world. Two models: authoritative server (server holds true game state; clients send inputs, server sends state updates) vs peer-to-peer (players send state to each other; no central authority). For competitive games: always use an authoritative server to prevent cheating. Architecture: dedicated game server per match (stateful process). Players connect via UDP (WebSocket for browser-based games). Each tick (20-60ms): server receives player inputs, advances game state, sends delta updates to all players. State sync: send only the delta (changes since last tick), not full state. Delta compression: bitmask indicating which fields changed. Lag compensation: if a player has 80ms latency, the server applies their input to the state 80ms ago (lag-compensated hit detection). Client-side prediction: client applies input locally immediately (before server confirmation) to hide latency. Server reconciliation: when the server’s authoritative state arrives, correct the client prediction if needed. Infrastructure: dedicated game servers run on VMs with aggressive autoscaling (match lifecycle = 20-60 minutes). Game server orchestration: Agones (Kubernetes-based) or custom fleet management. Players are assigned to the nearest game server region.
Leaderboards at Scale
Global leaderboard: rank all players by score. Redis sorted set: ZADD leaderboard {score} {player_id}. ZRANK for a player’s rank: O(log N). ZREVRANGE for top-K: O(log N + K). For 150 million players: a sorted set with 150M entries fits in ~10GB of Redis RAM. ZRANK returns a player’s global rank in O(log N). This is the canonical real-time leaderboard implementation. Periodic leaderboards (weekly, seasonal): use a separate sorted set per time window. On the reset: rename the old set to archive, create a new empty set. Sharding for very large leaderboards: partition by score range (shard 0: 0-1000 MMR, shard 1: 1000-2000, etc.). Rank = local_rank + sum of counts in lower shards. Friends leaderboard: for “rank among friends” — query scores for the player’s friend list (bounded set, typically < 500), sort in-memory. Friends list is stored in a graph DB or Redis set. Score writes: on match completion, batch-update all player scores in one pipeline. Rate: for 10K concurrent matches ending per hour, peak write rate is manageable (not every match ends simultaneously).
Player Profile and Persistence
Profile storage: PostgreSQL for player profiles (player_id, username, email, created_at, stats JSON). Write-through cache in Redis for frequently read profiles. Match history: append-only log in a time-series or columnar store (Cassandra, BigQuery). Each match record: match_id, players (array), outcome, duration, statistics per player. Query patterns: “last 20 matches for player X” — partition by player_id, sort by timestamp. Item/inventory system: NoSQL (DynamoDB) for player inventories (player_id → {item_id: quantity, …}). High write throughput from item drops. Conditional writes for atomic inventory updates. Session management: on match end, the game server POSTs the match result to the backend API. Backend validates, updates player MMRs, records match history, updates leaderboard. This is the critical path for data integrity — idempotent endpoints with match_id as idempotency key.
See also: Meta Interview Prep
See also: Snap Interview Prep
See also: Twitter Interview Prep