0) Problem Restatement
Design an online chess service. Players get matched with an opponent of similar skill, play a real-time game with a chess clock (e.g., 5 minutes each), and see each other's moves instantly. Games end by checkmate, resignation, draw or timeout (running out of clock time). If a player's connection drops, they should be able to reconnect and continue. Others can watch as spectators, and finished games are saved.
A nice way to answer (asked at OpenAI): start with a simple design for launch, then show how it changes as traffic grows.
1) Requirements
1.1 Functional
- Matchmaking by rating and time control (rated and unrated).
- Real-time moves with legal-move validation.
- A clock per player that the server controls.
- Reconnect to an ongoing game.
- Spectators, game history and rating updates after each game.
1.2 Non-Functional
- Low latency: moves appear for the opponent in under ~100–200 ms.
- Fairness: nobody gains time from network lag or tricks, and the server is the referee.
- Reliability: a server crash should not lose the game.
1.3 Scale Estimates
- 10M daily users, 500K games in progress at peak.
- A move every ~10 seconds per game → 50K moves/sec.
- Each move is tiny (about 100 bytes). Bandwidth is small; the challenge is lots of long-lived connections.
- Each server holds ~50K WebSocket connections, so we need tens of game servers.
1.4 API Design
POST /v1/matchmaking{ time_control: "5+0", rated: true }, then wait formatch_foundon the socket.- WebSocket
/v1/games/{game_id}: - client → server:
{ type: "move", move: "e2e4", seq: 17 } - server → clients:
{ type: "moved", move: "e2e4", fen, clocks: { white_ms, black_ms }, seq: 17 } GET /v1/games/{id}(history, PGN)
2) High-Level Architecture
2.1 Overview
- Matchmaking Service: queues players per time control and rating band and pairs them.
- Game Servers: each game lives on one game server that holds the board and clocks in memory. Both players (and spectators) connect to that server.
- Game Router: maps
game_id → game server, e.g., a Redis lookup or consistent hashing. - Game Store: saves moves (so a crashed game can be rebuilt) and finished games.
- Rating Service: updates Elo/Glicko ratings when a game ends.
- Spectator fan-out: pub/sub, so many watchers don't overload the game server.
2.2 Architecture Diagram
Architecture Diagram
flowchart LR
P1["Player White"] -->|"WebSocket"| GS["Game Server - owns game in memory"]
P2["Player Black"] -->|"WebSocket"| GS
P1 --> MM["Matchmaking Service"]
P2 --> MM
MM -->|"create game"| GR["Game Router"]
GR --> GS
GS -->|"append each move"| LOG[("Move log - Redis/Kafka")]
GS -->|"game over"| DB[("Game history DB")]
GS --> PS[("Pub/Sub")]
PS --> SP["Spectators"]
DB --> RT["Rating Service"]3) Data Model
games: game_id, white_id, black_id, time_control, status (active/finished),
result, started_at, ended_at
moves: game_id, seq, move (UCI "e2e4"), white_ms_left, black_ms_left, server_ts
ratings: user_id, time_control, rating, deviation, games_played
4) Key Flows
4.1 Matchmaking
- Players join a queue for their time control, stored per rating bucket (e.g., 1400–1500).
- Every second, the matchmaker pairs players in the same bucket. If someone waits too long, it widens the allowed rating gap gradually.
- It creates the game, picks a game server, and tells both players where to connect.
4.2 Making a move
- The client sends
{ move, seq }. - The game server checks it is that player's turn, the move is legal, and
seqis the expected next number (which drops duplicates). - It updates the board and the clocks: the mover's clock stops, and the opponent's starts, using server time.
- It appends the move to the move log, then broadcasts it to both players and spectators.
5) Deep Dive A — Clocks and fairness
- The server owns the clock. Clients only display a countdown.
- When a move arrives, the server subtracts the elapsed time from the mover's clock. Some sites add a small lag compensation (e.g., up to 100 ms) based on measured round-trip time, so slow connections aren't punished.
- Timeouts: the game server sets a timer for the current player's remaining time. If it fires before a move, the game ends on time. The server decides, not the client.
6) Deep Dive B — Reconnects and crashes
- Reconnect: the client reconnects with
game_idand its lastseq. The server sends the full position and clocks, plus any missed moves. The player's clock keeps running while they are disconnected; that is the rule. - Game server crash: because every move was appended to a durable move log before broadcast, the router assigns the game to another server. That server rebuilds the board from the log, and players reconnect automatically. Clock time lost during the switch can be credited back.
- Evolving the design:
- Launch: one server holds everything, with Postgres for games. Simple.
- Growth: many game servers, sticky routing by game, Redis for queues and move logs.
- Large scale: regional clusters (match players in the same region for low latency), a separate spectator fan-out, and async rating and history pipelines.
7) Trade-offs & Alternatives
| Decision | Choice | Why | Alternative |
|---|---|---|---|
| Game state | In memory on one owning server | Fast, simple ordering | Stateless servers + shared DB per move: slower, lock contention |
| Transport | WebSockets | Two-way, low latency | Polling: laggy, wasteful |
| Durability | Append move before broadcast | Recover after crash | Save only at game end: lose games on crash |
| Clock | Server-authoritative | Fair, cheat-resistant | Client clocks: easy to cheat |
8) Common Follow-up Questions
- "Undo / takeback?" It needs the opponent's consent. Pop the last move from the in-memory state and append an "undo" event to the log.
- "Anti-cheat?" After games, compare moves with a chess engine. Very high engine-match rates for a player get flagged for review.
- "Leaderboard?" Keep a sorted set of ratings per time control and update it asynchronously when games end.
9) Wrap-Up
Pair players by rating in matchmaking, then give each game one owning game server that holds the board and a server-authoritative clock in memory. Players connect over WebSockets. Validate every move with sequence numbers and append it to a durable log before broadcasting, so reconnects and server crashes can restore the exact game.