Architecture Overview
A multiplayer game backend handles: matchmaking (finding players with similar skill), game sessions (real-time state sync during a match), leaderboards (global and friend rankings), and player data persistence (inventory, progress, achievements). The most challenging part is the real-time game session — thousands of concurrent matches, each requiring low-latency state synchronization between players.
Matchmaking
Matchmaking groups players with similar skill ratings (ELO or MMR — Match Making Rating). Algorithm: players join a matchmaking queue. A matchmaking service polls the queue and groups players by skill bracket. Skill brackets: partition players into windows (e.g., MMR 1000-1100, 1100-1200). As queue wait time increases: expand the bracket (after 30 seconds, accept MMR 900-1200). After 2 minutes: accept any MMR. This ensures fast matches at peak hours and eventual matches during off-peak. Implementation: Redis Sorted Set per game mode with score=MMR. ZRANGEBYSCORE to find players in a bracket. On match: atomically remove selected players from the queue (ZREM), create a game session. Regional matchmaking: match players on the same geographic region first (reduces latency). Redis Geo for regional grouping.
Game Session Architecture
Each match runs on a dedicated Game Server (a long-running process managing the game state for one match). Game server selection: a Fleet Manager maintains a pool of game servers (EC2 instances, Kubernetes pods). On match creation: find a server with available capacity in the appropriate region. Assign the match. Game server communication: players connect directly to their assigned game server via UDP (low latency, game-appropriate). WebSockets for less latency-sensitive games (card games, turn-based). Game loop: the game server runs at a fixed tick rate (20-60 ticks/second for action games). Each tick: receive player inputs, advance game state, broadcast updated state to all players.
State synchronization: client-side prediction (client immediately applies the player’s own input locally without waiting for server confirmation). Server reconciliation: periodically receives the authoritative server state and corrects any divergence. Lag compensation: the server rewinds game state to the player’s perspective time to check if a hit was valid. This prevents high-latency players from being penalized for their connection.
ELO and MMR Updates
After each match: update each player’s MMR. ELO formula: expected_score = 1 / (1 + 10^((opponent_MMR – player_MMR)/400)). actual_score = 1 (win) or 0 (loss) or 0.5 (draw). new_MMR = old_MMR + K * (actual_score – expected_score). K-factor: 32 for new players (converges quickly), 16 for experienced players. Store MMR history for analytics. Leaderboard update: after MMR change, update the global leaderboard Redis Sorted Set: ZADD leaderboard player_id new_MMR. Global rank: ZREVRANK leaderboard player_id. Top-100: ZREVRANGE leaderboard 0 99 WITHSCORES.
Leaderboards
Redis Sorted Set is the canonical leaderboard implementation. ZADD O(log n), ZREVRANK O(log n), ZREVRANGE O(log n + K). Global leaderboard: a single sorted set for all players. Friend leaderboard: ZADD friend:{user_id}:leaderboard friend_id score for each friend. Update on each friend’s MMR change. Pagination: ZREVRANGE leaderboard offset offset+page_size-1. Seasonal leaderboard: create a new sorted set each season (leaderboard:{season_id}). Archive the previous season’s top-100 to a database. Player rank context: ZREVRANGE leaderboard player_rank-5 player_rank+5 to show the player and their neighbors.
Interview Tips
- Game sessions require dedicated servers (not serverless) — a persistent process maintains game state across a match. Stateless servers cannot handle real-time game state.
- UDP vs TCP: action games use UDP (lower latency, acceptable packet loss). Card games, strategy: TCP/WebSockets (reliability matters more than latency).
- The leaderboard is a classic Redis Sorted Set problem. Know ZADD, ZREVRANK, ZREVRANGE, and ZRANGEBYSCORE — they cover 99% of leaderboard operations.
{
“@context”: “https://schema.org”,
“@type”: “FAQPage”,
“mainEntity”: [
{
“@type”: “Question”,
“name”: “How does matchmaking ensure fair games with low wait times?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Skill-based matchmaking (SBMM) groups players with similar MMR (Match Making Rating). The fundamental trade-off: narrow skill brackets produce fairer games but longer wait times. Wide brackets produce faster matches but unfair skill gaps. Solution: time-based bracket expansion. Start with a narrow bracket (MMR +/- 50). After 30 seconds: expand to +/- 150. After 90 seconds: expand to +/- 300. After 3 minutes: accept any MMR (prefer playability over fairness). Implementation: Redis Sorted Set per game mode/region with score=MMR. Matchmaking service runs every 2 seconds: ZRANGEBYSCORE to find players in the current bracket for each waiting player. Group them into teams. On match found: atomically remove players from the queue (ZREM in a Redis transaction). Regional matching: run separate queues per region (NA, EU, ASIA) to minimize network latency.”
}
},
{
“@type”: “Question”,
“name”: “How do dedicated game servers handle real-time state for thousands of concurrent matches?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Each active match runs on a dedicated game server process. Fleet management: a Game Server Manager (e.g., Agones on Kubernetes, or AWS GameLift) maintains a pool of game server instances per region. Instances can host multiple game server processes (one per match). Allocation: when a match starts, the matchmaking service requests a game server allocation from the manager. The manager assigns an available server process and returns its IP:port. Players connect directly to this IP:port (bypasses the matchmaking server). Game loop: the server runs at a fixed tick rate (60Hz for FPS, 20Hz for battle royale, 1Hz for turn-based). Each tick: process inputs from all players, compute the authoritative game state, broadcast delta updates to players. State broadcast: send only changed state (delta), not the full state each tick, to reduce bandwidth.”
}
},
{
“@type”: “Question”,
“name”: “How does ELO rating work and how is it updated after a match?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “ELO was originally designed for chess. Each player has a rating (MMR). Expected score for player A vs B: E_A = 1 / (1 + 10^((MMR_B – MMR_A)/400)). If MMR_A = MMR_B: E_A = 0.5 (50% win probability each). K-factor controls how much a single game affects rating. High K (32): new players, rating changes quickly. Low K (16): experienced players, stable rating. After a match: new_MMR_A = old_MMR_A + K * (actual_score – expected_score). Actual score: 1 (win), 0 (loss), 0.5 (draw). Win against a higher-rated opponent: large MMR gain. Win against a lower-rated: small gain. Lose against lower-rated: large loss. Team games (League of Legends, Valorant): use TrueSkill (Microsoft’s rating system) which handles team uncertainty better than ELO. TrueSkill models skill as a Gaussian distribution (mean and variance) rather than a single number.”
}
},
{
“@type”: “Question”,
“name”: “How do you build a global leaderboard that scales to millions of players?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Redis Sorted Set is the standard implementation. ZADD leaderboard player_id mmr: O(log n). ZREVRANK leaderboard player_id: O(log n), returns 0-indexed rank. ZREVRANGE leaderboard 0 99: top-100 players. ZRANGEBYSCORE: players in an MMR range. Seasonal leaderboards: create a new sorted set each season (leaderboard:2025_s1). Archive top-1000 to a relational database at season end. Friend leaderboard: maintain per-user sorted sets (friends:{user_id}) updated when a friend’s MMR changes. On friendship: add to each other’s friend leaderboard set. On MMR change: ZADD friends:{all_friend_ids} new_mmr. Fan-out for popular players (streamers with 100K friends): use a pull model instead — on friend leaderboard load, fetch the scores of the user’s friends in batch (ZMSCORE) rather than maintaining a pre-built set per user.”
}
},
{
“@type”: “Question”,
“name”: “How does client-side prediction prevent laggy gameplay?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Without client-side prediction: the player presses ‘move right’, sends the input to the server, waits for the server to respond with the new position, then renders the movement. At 100ms round-trip latency: the player experiences 100ms of unresponsive controls — unacceptable for action games. Client-side prediction: the client immediately applies the player’s input locally (move the character on screen) without waiting for server confirmation. Simultaneously, send the input to the server. The server processes the input authoritatively and sends back the true position. If the client’s predicted position matches (usually): no visible correction. If they diverge (due to collision with another player the client didn’t know about): smoothly interpolate to the server’s authoritative position. This correction (rubber-banding) is minimized by the server sending updates at high frequency and the client using dead reckoning (predict other players’ positions based on their last known velocity).”
}
}
]
}
Asked at: Meta Interview Guide
Asked at: Snap Interview Guide
Asked at: Twitter/X Interview Guide
Asked at: Netflix Interview Guide