Online Chess
Design turn-based online chess with authoritative legal moves, clocks, reconnects, and game history.
Interview scope and guarantees
Create a game, match players, accept legal moves, maintain clocks, reconnect and display spectators. The server decides move legality, turn order and result. Client animations may be optimistic; client clocks cannot determine the winner.
Capacity worksheet
Assume 100,000 concurrent games with a move every 5 seconds per game: 20,000 moves/s. At 200 bytes/move that is 4 MB/s raw. A famous match can have far more spectators than players, so separate authoritative game commands from spectator fanout.
Concrete API contract
POST /games {opponentId,timeControl}
POST /games/{id}/moves {clientMoveId,expectedPly,from,to,promotion?}
GET /games/{id}?afterPly=...
POST /games/{id}/resignData model and access paths
games(id PK,white_id,black_id,position,ply,state,owner_epoch,turn_started_at,remaining_times,version)
moves(game_id,ply,actor_id,client_move_id,request_hash,notation,accepted_at); UNIQUE(game_id,ply); UNIQUE(game_id,actor_id,client_move_id)
results(game_id UNIQUE,winner,reason,rating_version)Evolve a solution and explain each change
Figure — Three architecture decisions for Online Chess, including the pressure each introduces.
Step 1: Authoritative game state
Validate moves against the current board and player turn. Two clients cannot both define the next legal state.
Step 2: Order moves and clocks
Apply move identity, expected position version, and server clock rules together. Client timestamps and duplicated packets can corrupt turn order.
Step 3: Recover sessions
Replay committed moves after reconnect and separate spectators from players. Socket presence is not proof that a player forfeited.
Responsibility overview
Figure — Connected responsibilities for Online Chess. Trace the authoritative and derived paths separately.
Worked end-to-end scenario
White sends move e2-e4 at board version 12, then retries after a lost response. The authority validates legality, turn, version, and remaining time and records one move identity. The retry returns version 13 rather than consuming a second turn. Black reconnects and replays from version 12. Clock accounting uses a defined server admission instant and monotonic elapsed time, with a stated lag/timeout policy. A late move received after expiry cannot be accepted based solely on the player’s device clock. Spectator delivery can lag without changing the authoritative result.
Why these access paths matter
Store game ID, players, board version, clock state, outcome, and ordered moves. Use game as the serialization domain and actor/client move ID for retries. A board snapshot plus move suffix supports recovery; both need a consistent version boundary. Presence, chat, and spectator subscriptions are independent from legal move state.
Build the baseline first
- Player move → game owner → legality and clock check.
- Atomic position update → durable move → opponent.
- Reconnect → position snapshot and move suffix.
Evolve the design under load
- Game-key routing → independent state owners.
- Spectator stream → regional gateways.
- Completed games → rating jobs and analysis queue.
Defend the hardest decision
Validate expected ply, player identity, legal movement and remaining time in one authoritative transition. Persist the clock baseline rather than decrementing a database row every millisecond. A server process uses a monotonic clock for elapsed time within its lifetime; failover needs a documented persisted-time policy and synchronized server clocks. The chosen lag-compensation rule must be explicit and abuse-resistant.
Failure and recovery analysis
A move commits but its response is lost. A retry with the same clientMoveId returns the committed move. A different move for the old ply is rejected. An old game owner returning after failover must be fenced from committing moves. Spectator lag can grow without delaying the players, provided buffers are bounded.
Security and privacy boundary
Authorize player actions, protect matchmaking from abuse, and separate anti-cheat analysis from the low-latency move path.
Interview follow-ups with reasoning
Question: How do you resolve simultaneous resign and move?
Show answer and explanation
Answer: Serialize commands at the game owner.
Question: How do you rebuild a board?
Show answer and explanation
Answer: Replay validated moves from a versioned initial position.
Question: How do you update ratings safely?
Show answer and explanation
Answer: Deduplicate by completed game and rating algorithm version.
Operate and verify the design
Move latency, stale-ply rejections, clock anomalies and duplicate rating jobs.
Fail over the game owner during a move and verify one committed ply and a consistent clock baseline.
A second scenario to test transfer
White and Black both submit moves against version 40 due to a client bug. Only White is side to move. The game authority validates White’s move and commits version 41. Black’s stale command fails with current version and board. If White retries after losing the response, the same request ID returns the committed ply rather than applying another move.
White’s move request commits, but the response is lost. What happens when the client retries and Black submits against the old version?
Show answer and explanation
Answer: White retries with the same request ID and receives the already committed ply. Black’s command is checked against the authoritative side and version; it cannot overwrite White’s move. The next valid move uses the new version. This provides idempotent command handling and one game order.
Compare alternatives
| Event | Authoritative? | Recovery |
|---|---|---|
| Board animation | No | Replace with snapshot/event |
| Committed ply | Yes | Replay by ply cursor |
| Displayed clock | Approximation | Server turn deadline |
| Presence | Ephemeral | Reconnect and refresh |
A design-changing exercise
Both clients say their clocks had time remaining. Which timestamp decides the result?
Show answer and explanation
Answer: Use the defined authoritative server admission and clock policy. Client clocks are not trustworthy enough to own the game outcome.
Design workshop: moves, clocks, and owner recovery
Core scope is standard chess, matchmaking, timed games, reconnect and spectators. Variants and anti-cheat model training are extensions. Use a tested rules library for legality; a client move is a proposal. Choose server admission time as the clock boundary with no latency compensation in this teaching baseline, and disclose that policy. Client animation and displayed clocks remain estimates.
Store FEN-like board state plus move history, castling rights, en-passant state, side to move, half-move counter and repetition evidence. Piece locations alone cannot reconstruct all legal state. Validate check, promotion, castling and en-passant under the selected rules; distinguish claimable and automatic draws using documented rules. Do not turn every repeated board diagram into a draw without comparing legal-position state.
The move identity is unique(game,actor,client_move_id) with fingerprint. Before rejecting expectedPly, check whether that exact command already committed and return its original ply. Then lock game state, validate actor/turn/version/owner epoch/time/legality and atomically append the move with updated board and clock. A retry cannot consume a second turn.
Figure — Lost response returns the already committed move.
With 60 seconds remaining, White starts at monotonic t=100 and the server admits a legal move at t=104. Charge four seconds and add a two-second increment, leaving 58 seconds. The increment applies only after an accepted legal move. A rejected illegal request does not pause the clock. If a move arrives after the declared remaining time expires, reject under the selected policy; claimed device send time cannot override it.
Figure — Clock accounting and failover policy.
For this baseline choose an explicit operational pause while game authority recovery is uncertain, recording the pause event and resuming with persisted remaining time adjusted to the last trusted clock checkpoint. This trades fairness precision for transparent recovery; it cannot claim submillisecond time accuracy after losing an unpersisted interval. An alternative is a trusted durable wall deadline with bounded skew and owner epochs, but its error must be stated and tested. Never subtract one machine's monotonic timestamp from another's.
Matchmaking stores region/rating/time-control queues and uses an atomic pair claim to prevent one player entering two games. A declared expanding rating band reduces long waits. Ratings update once under unique(game,rating_version). An illustrative Elo update uses expected score E=1/(1+10^((opponent−player)/400)), then R_new=R+K(S−E); equal ratings with K=20 and a win gain 10. This is a chosen teaching policy, not a claim about a chess platform's real rating algorithm.
Spectator gateways consume committed ply events separately from player commands. A reconnect supplies afterPly; return suffix or board snapshot+cursor when history no longer supports it. Bound spectator buffers and never let a million watchers exhaust move authority. Privacy/authorization still applies to private games.
Exercise: White's request committed ply 13 but response was lost. Its retry carries expectedPly=12. Reject as stale or replay?
Show answer and explanation
Answer: Look up the stable actor/request identity first and return committed ply 13. Only an unseen command with old expectedPly conflicts. This ordering is part of the API contract, not just a uniqueness constraint.
FIDE rules are the source for chess legality/draw distinctions; the online clock and recovery policy above is a separate system choice.
Technical references
White’s move request commits, but the response is lost. What happens when the client retries and Black submits against the old version?
Your design draft
Clarify assumptions, explain your approach, and test the difficult cases. Save your draft, then compare it with the study notes.
Self-review checklist
Self-guided practice. Automated AI feedback and code execution are not connected.