CASE STUDY

Online Chess Platform (chess.com)

6 min read·1,002 words·Intermediate

Asked at

10 candidate reports between Nov 2025 and Aug 2026

How to use this case study

SDE-2 / Mid

Explain matchmaking, how moves travel over WebSockets, how the server validates moves, and how games are saved.

SDE-3 / Senior

Go deeper on server-authoritative clocks, reconnects, recovering a game after a server crash, and routing all players of a game to one server.

Staff / Principal

Discuss scaling to millions of concurrent games, spectators, rating updates, anti-cheat and evolving the design from launch to scale.


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 for match_found on 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

  1. Players join a queue for their time control, stored per rating bucket (e.g., 1400–1500).
  2. Every second, the matchmaker pairs players in the same bucket. If someone waits too long, it widens the allowed rating gap gradually.
  3. It creates the game, picks a game server, and tells both players where to connect.

4.2 Making a move

  1. The client sends { move, seq }.
  2. The game server checks it is that player's turn, the move is legal, and seq is the expected next number (which drops duplicates).
  3. It updates the board and the clocks: the mover's clock stops, and the opponent's starts, using server time.
  4. 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_id and its last seq. 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

DecisionChoiceWhyAlternative
Game stateIn memory on one owning serverFast, simple orderingStateless servers + shared DB per move: slower, lock contention
TransportWebSocketsTwo-way, low latencyPolling: laggy, wasteful
DurabilityAppend move before broadcastRecover after crashSave only at game end: lose games on crash
ClockServer-authoritativeFair, cheat-resistantClient 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.

More Case Studies

Practice with a Mock Interview

Apply what you learned in a live system design mock interview with our AI interviewer.

Start System Design Interview →