Design Chess
Design a chess LLD: board, pieces, legal moves, specials, and the check filter that stops Alice from leaving her king in danger.
Page content
Alice (White) develops her knight. Bob’s king is in check. The hop is a clean 2-by-1.
If move() only asks “do knights go 2-by-1?” and not “is my own king safe after this?”, she can castle through check or leave herself in check. The game is already illegal. Geometry is not chess.
This is an LLD interview: a board, six piece types, a move that is legal only when Alice’s king is safe afterward. It is not Stockfish, not a WebSocket lobby, not a chess.com clone. Same muscle as Design a Parking Lot or Design Snake and Ladder — objects and a state machine, not a multiplayer product.
The main design question is:
How do we represent the board, generate legal moves (including specials), and decide check, checkmate, and stalemate?
We will start with a switch on piece type and watch promotion explode it. Strategies, a king-safety filter, castling rights, and a small state machine appear when that switch is no longer enough.
1. Clarify the problem
“Design chess” can mean an engine that plays, a realtime app with clocks and chat, or a rules object two humans share. The questions that change the objects:
- Standard chess, or variants (960, crazyhouse)?
- Two human players, or an AI side?
- Do we need a clock? Increment? Flag fall?
- Undo? PGN export? A UI, or just
move()? - Draw by agreement, 50-move, threefold repetition?
- One game in memory, or a server of rooms?
If the interviewer gives no extra constraints, I would state:
Rules FIDE standard, one game
Players Two: Alice White, Bob Black
Variants Out of scope
Engine / eval Out of scope (not Stockfish)
Network Out of scope (not sockets, not a lobby)
Clocks Optional wrapper; not part of legality
Draw extras Name 50-move and threefold; do not implement
Undo Nice if the move log is rich; not required for v1
A chess server with matchmaking is HLD. Say so and stay on the board. Kafka does not belong in this picture.
2. Functional requirements
The system must:
- Start a game from the standard position, White to move.
- Accept a move from the side whose turn it is, or reject it.
- List legal moves for the side to move (for a UI, or for mate tests).
- Detect check, checkmate, and stalemate after every legal move.
- Allow resign.
- Handle the specials: pawn double-step, en passant, castling, promotion.
Not in v1: an evaluation function, opening books, bitboards, a clock product, takebacks as a social feature, 50-move or threefold as implemented draws. Those sit on the same Board and Move. They do not change isLegal.
3. Non-functional requirements
This is not a QPS problem. Interviewers want correct rules, not a cluster.
| Requirement | Target |
|---|---|
| Correctness | Every accepted move is legal FIDE chess |
| Speed | Milliseconds; 64 squares, ~20–40 legal moves |
| Concurrency | One game; serialize turns, do not “CAS the knight” |
| History | Move list is enough for undo and a later PGN |
| Clock | Server time if they insist; never the client |
A wrong castle is a failed interview. A 2 ms move generator is a hobby. Prefer an 8×8 array you can draw on the whiteboard over bitboards you cannot debug in fifteen minutes.
Edge cases
- Alice moves a piece that does not get her king off a check.
- Alice castles through a square Bob attacks, or out of check.
- A pinned bishop “can” slide — until the king-safety filter.
- Pawn on the 7th rank: four promotions, or a capture-promotion.
- En passant is legal only on the turn after a double-step.
- Bob captures Alice’s rook and she loses that castle right.
moveis called when it is not that side’s turn.moveafterCHECKMATEorRESIGNED.- Two legal escapes from check; Alice picks neither.
4. Scale
Say the numbers so nobody spends the interview on throughput.
Squares 64
Pieces 32 at start, fewer later
Legal moves / turn typically 20–40, not millions
Games one object in this interview
Players two
Generating every pseudo-legal move and throwing away those that leave the king in check is O(pieces × directions × 8) plus one “make / unmake / is the king safe?” per candidate. That is fine. An engine’s alpha-beta search is a different problem. If they say “ten thousand rooms,” each room is this Game. You still do not introduce a message bus.
Retention is the move log, not a data warehouse. Keep it for undo and PGN. You do not need a replay cluster.
5. Entities / what we store
Six facts, and a derived status.
Color WHITE | BLACK
PieceType KING QUEEN ROOK BISHOP KNIGHT PAWN
Square file 0–7 (a–h), rank 0–7 (1–8)
Piece color, type (or a subclass per type)
Move from, to, promotion?, flags, captured?
Board 8×8 cells, side to move, castle rights, en passant square
Game board, status, move log
A move is the source of truth for “what changed.” The board is the position you can see. Status (CHECK, CHECKMATE, …) is derived after the move lands: recompute check and whether the opponent has a legal reply. If status and the legal-move list disagree, the list wins.
Board: Alice king e1, Bob rook e8, e-file empty
Alice tries Nb1-c3
filter: after Nc3, e1 is still attacked
reject
Do not store “Alice is in check” as a flag you set by hand. Compute it. Castle rights and the en passant target are real state — they are not recoverable from the piece grid alone.
Squares look like e4 in speech and (file=4, rank=3) in code (0-based). Pick one encoding and convert at the edge.
6. APIs or class methods
In LLD you show a service, not HTTP. A chess “REST API” is a UI adapter.
start() -> Game
move(from, to, promotion?) -> MoveResult
legalMoves(color?) -> [Move]
resign(color) -> Game
status() -> GameStatus
Start
Request:
start()
Response:
{ "status": "IN_PROGRESS", "to_move": "WHITE", "fen": "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" }
FEN is optional flavor. The object is a Game, not a string.
Move
move({ "from": "b1", "to": "c3" })
Accepted:
{ "move": "Nc3", "status": "IN_PROGRESS", "to_move": "BLACK", "in_check": false }
Illegal (leaves own king in check):
{ "error": { "code": "ILLEGAL_MOVE", "message": "Own king would be in check" } }
Wrong side is NOT_YOUR_TURN. Already over is GAME_OVER. A pawn on the 8th rank without promotion is PROMOTION_REQUIRED (or you default to queen — say which). The UI highlights legalMoves(). It must not invent a castle the filter rejected.
7. Start with a switch in Game — watch it explode
The first design everyone writes:
function move(from, to):
piece = board.at(from)
if piece.color != sideToMove: throw NOT_YOUR_TURN
switch piece.type:
KNIGHT:
if not isTwoByOne(from, to): throw ILLEGAL
BISHOP:
if not isClearDiagonal(from, to): throw ILLEGAL
PAWN:
if not isOneForward(from, to): throw ILLEGAL
...
board.put(to, piece)
board.clear(from)
flipSide()
It is easy to draw and easy to break.
a b c d e f g h
8 . . . . r . . k
7 . . . . . . p p
6 . . . . . . . .
5 . . . . . . . .
4 . . . . . . . .
3 . . . . . . . .
2 . . . . . . P P
1 . N . . K . . R
Alice to move. Bob’s rook on e8 owns the e-file.
Nb1-c3 is a knight hop. The switch says yes. After the hop, e1 is still attacked. Alice has left herself in check. The position is illegal.
The same switch has nowhere honest to put:
- promotion (four piece types appear on
to); - en passant (the captured pawn is not on
to); - castling (two pieces move; three squares must be safe);
- pins (the bishop’s diagonal is clear, and still illegal).
You will add if on if until Game is the rulebook and every new special touches every case. That is the parking-lot naive scan: it looks like the domain and does not protect the invariant.
switch in Game wrong (and it grows)
geometry only wrong (misses king safety)
piece.pseudoLegal + filter correct, interview-sized
bitboards + magic attacks engine, not 45 minutes
We will use the third. The first is what you write on the board so you can cross it out.
8. Piece strategy: pseudo-legal, then a filter
Each piece knows how it moves, not whether the game is still legal after it does.
A pseudo-legal move is geometry plus “do not capture your own piece,” including the specials that piece owns (pawn double, pawn capture, castle attempt). It does not ask “is my king safe?”
A legal move is a pseudo-legal move that, when applied and reverted, leaves Alice’s king unattacked.
interface Piece
color()
type()
pseudoLegalMoves(board, from) -> [Move]
Knight.pseudoLegalMoves:
for each of 8 L-shaped deltas:
sq = from + delta
if onBoard(sq) and board.at(sq) is empty or Bob:
yield Move(from, sq)
Sliding pieces (bishop, rook, queen) walk a ray until they hit an edge, a friend (stop), or an enemy (capture and stop). The king is one step in eight directions, plus castle attempts if rights and path-empty say so. Pawns are the messy ones — they belong in Pawn, not in Game.
Game owns the filter:
legalMoves(color):
out = []
for each (square, piece) of color:
for m in piece.pseudoLegalMoves(board, square):
board.apply(m)
safe = not board.isAttacked(kingOf(color), opponent(color))
board.unapply(m)
if safe: out.append(m)
return out
move(m):
if m not in legalMoves(sideToMove): throw ILLEGAL_MOVE
board.apply(m)
log.append(m)
recomputeStatus()
apply / unapply must restore castle rights, the en passant square, and the captured piece. If unapply is sloppy, a pin test corrupts the board and every later move is junk.
Why not teach every piece about check? Because a pinned bishop still attacks through the pin for the purpose of Bob’s king. Attack generation and legal-move generation are different questions. The filter keeps them apart.
Alice king e1, Alice bishop e2, Bob rook e8
Bishop on e2 is pinned. Its pseudo-legal list includes e3, e4, …
Filter throws those away: after the bishop leaves e, e1 is checked.
The bishop still attacks e3 for “does Bob’s king step onto e3?”
That last line is the Senior distinction. Pins fall out of the filter. You do not need a pin table.
9. Board representation
An 8×8 array of Piece | empty is the interview board. Files a–h left to right, ranks 1–8 bottom to top when White sits at the bottom.
a b c d e f g h
8 r n b q k b n r
7 p p p p p p p p
6 . . . . . . . .
5 . . . . . . . .
4 . . . . . . . .
3 . . . . . . . .
2 P P P P P P P P
1 R N B Q K B N R
White = uppercase. Black = lowercase.
Alice (White) on ranks 1–2. Bob (Black) on ranks 7–8.
Index with board[rank][file] or board[rank * 8 + file]. Either is fine. Bitboards (one 64-bit mask per piece type) are how engines generate attacks. They are the wrong first drawing: you cannot see Alice’s knight.
Side state that is not in the grid:
toMove WHITE | BLACK
castleRights White K, White Q, Black K, Black Q
enPassant square or none
halfmoveClock optional (50-move)
fullmoveNumber optional (PGN)
After e2-e4, enPassant becomes e3 and clears on the next completed turn if unused. After Alice’s king moves, both of her castle flags die. After her h-rook moves or is captured, only White K dies.
Square file, rank
a1 = (0,0) h1 = (7,0) e4 = (4,3) e8 = (4,7)
onBoard 0 ≤ file < 8 and 0 ≤ rank < 8
Board.at and Board.place are the only ways to touch cells. Tests build positions with place, not by poking the array.
10. Specials: double, en passant, castle, promotion
Ordinary geometry is not enough. Four rules change extra state. Put them on the piece that owns them, then run the same king-safety filter.
Pawn double-step
From rank 2 (Alice) or rank 7 (Bob), a pawn may step two squares if both the next square and the landing square are empty. It never jumps a piece. The move sets enPassant to the skipped square.
Alice pawn e2, e3 empty, e4 empty
e2-e4 legal
enPassant = e3
Alice pawn e2, Bob knight on e3
e2-e4 illegal (path blocked)
En passant
If Bob just double-stepped a pawn onto a file next to Alice’s pawn, she may capture as if he had stopped on the skipped square. The captured pawn is not on to. apply must remove it. unapply must put it back.
a b c d e f g h
5 . . . . P p . . Alice pawn e5, Bob just played f7-f5
4 . . . . . . . .
enPassant = f6
Alice exf6 e.p. → pawn lands f6, Bob’s pawn on f5 disappears
Only on the immediate next turn. If Alice plays something else, enPassant clears and the capture dies. The filter still applies: if taking en passant opens her king to a rook on the 5th rank, the move is illegal. That is a real FIDE case. Your apply/unapply has to be good enough to see it.
Castling
Alice may castle if all of these hold:
- Rights. Her king and that rook have never moved (flags, not “they sit on home squares”).
- Path empty. Squares between king and rook are empty.
- Not in check. e1 is not attacked.
- Not through check. The square the king crosses is not attacked (f1 for O-O, d1 for O-O-O).
- Not into check. The landing square is not attacked (g1 or c1).
O-O moves the king e1-g1 and the rook h1-f1. O-O-O moves the king e1-c1 and the rook a1-d1. One Move with a CASTLE flag; apply moves both pieces.
a b c d e f g h
8 . . . . . . . k
7 p p p p p p p p
6 b . . . . . . .
5 . . . . . . . .
4 . . . . . . . .
3 . . . . . . . .
2 P P P P P P P P
1 R . . . K . . R
Alice wants O-O. Rights yes. f1 and g1 empty.
Bob’s bishop on a6 attacks f1 (a6–b5–c4–d3–e2–f1).
Castle through check. Illegal.
pseudoLegalMoves on the king can yield the castle if (1) and (2) hold. The filter — and an explicit “e1 / f1 / g1 attacked?” check — kill (3)(4)(5). I would test the through-check case even if the generic filter would also reject landing on a checked g1; f1 is the square people forget.
Rights update on apply:
king moves → that color loses both rights
rook moves → that color loses that side
rook captured → opponent loses that side
Sitting on e1 and h1 after a king trip to f1 and back does not restore rights. The flag is history, not a glance at the grid.
Promotion
When Alice’s pawn reaches rank 8 (Bob, rank 1), it must become a queen, rook, bishop, or knight of her color. There is no “stay a pawn.”
move(e7, e8, promotion=QUEEN)
move(e7, d8, promotion=KNIGHT) // capture + promote
Move.promotion is required on that rank. Defaulting to queen is a UI choice. The object should not silently pick. After promotion, the filter runs as usual: a new queen that does not cover her king is still illegal.
Do not implement these as four extra switch cases in Game. Pawn emits double, capture, en passant, and promotion candidates. King emits castle candidates. Game only filters and applies.
11. Check, checkmate, stalemate
Three questions, in this order.
Is the square attacked? Walk Bob’s pieces and ask whether any of them attacks that square. Pawns attack diagonally, they do not attack the square in front. Knights jump. Sliders need a clear ray. Do not call legalMoves here — you will recurse into “is the king safe?” forever.
isAttacked(square, byColor):
for each (from, piece) of byColor:
if square in piece.attacks(board, from):
return true
return false
inCheck(color):
return isAttacked(kingSquare(color), opponent(color))
attacks is almost pseudoLegalMoves with two edits: pawns use capture squares only, and you may ignore “would this leave my king safe?” A pinned rook still checks Alice if it sees e1.
Checkmate: Alice is in check and legalMoves(WHITE) is empty. Every attempt to step the king, block, or capture fails the filter.
Stalemate: Alice is not in check and legalMoves(WHITE) is empty. The game is a draw. Forgetting the “not in check” clause turns a stalemate into a fake mate.
after Alice’s legal move:
if Bob in check:
if Bob has no legal move: CHECKMATE (Alice wins)
else: CHECK
else:
if Bob has no legal move: STALEMATE (draw)
else: IN_PROGRESS
Check is not a third kind of move. It is a property of the position Bob is about to play from. A UI can flash the king. move() still goes through the same filter.
Check king attacked, at least one legal reply
Checkmate king attacked, zero legal replies
Stalemate king safe, zero legal replies
Double check is free: two attackers, and the filter will usually leave only king moves. You do not need a special case.
12. State machine for Game
One machine. Do not encode “White to move and in check” as twelve enums.
READY
│ start()
▼
IN_PROGRESS ──────► CHECK ──────► IN_PROGRESS
│ │
│ │ no legal reply
│ ▼
│ CHECKMATE
│
│ no legal reply, not in check
▼
STALEMATE
IN_PROGRESS / CHECK ── resign(color) ──► RESIGNED
READY is the constructed object before start(), or the standard position with no move yet — pick one and stick to it. I treat start() as “clocks may start; White may move,” and the first move stays in IN_PROGRESS unless it happens to check.
Terminal states: CHECKMATE, STALEMATE, RESIGNED. move is illegal there. resign in a terminal state is a no-op or GAME_OVER — say which. I would no-op and return the same status.
IN_PROGRESS / CHECK → move(legal) → recompute
IN_PROGRESS / CHECK → move(illegal) → unchanged (throw)
IN_PROGRESS / CHECK → resign → RESIGNED
CHECKMATE / STALEMATE / RESIGNED → no board writes
Illegal: CHECKMATE → IN_PROGRESS, accepting a move that leaves the mover in check, castling after the king has already moved. CHECK is a decorating state on an otherwise live game. Some teams keep only IN_PROGRESS plus inCheck. The user-facing machine above is easier to talk through. Internally, inCheck plus “legal list empty” is enough to derive it.
Draw by agreement is a handshake around this machine (DRAW), not a new move type. 50-move and threefold read the log; they are extra terminals you name if asked.
13. Move log, undo, PGN
apply mutates. The log is how you remember what to undo.
Move
from, to
piece
captured? // piece + square (e.p. square ≠ to)
promotion?
flags // QUIET | CAPTURE | DOUBLE | EN_PASSANT | CASTLE
prevCastleRights
prevEnPassant
Undo is unapply(log.pop()). You restore the grid and the two side fields. A log that stores only from/to cannot undo en passant or a lost castle right.
PGN is a serialization of that log (1. e4 e5 2. Nf3 ...). Mention it. Do not write a PGN parser in this interview. FEN snapshots the current position; PGN snapshots the story. The object graph is still the source of truth.
If they want takeback: undo() is legal in IN_PROGRESS and CHECK, not after mate, unless you are debugging a test. Two undos after 1. e4 e5 return the start. The filter does not change.
14. Class sketch
Ideas, not a framework dump. One Game. Two players call it in turn.
Color WHITE, BLACK
PieceType KING, QUEEN, ROOK, BISHOP, KNIGHT, PAWN
Square file, rank
plus(df, dr) -> Square | off
algebraic() -> "e4"
Piece color, type
Knight, Bishop, Rook, Queen, King, Pawn
pseudoLegalMoves(board, from) -> [Move]
attacks(board, from) -> [Square]
Move from, to, piece, captured?, promotion?, flags
prevCastleRights, prevEnPassant
Board
at(square) -> Piece?
apply(move) / unapply(move)
kingSquare(color) -> Square
isAttacked(square, by) -> bool
inCheck(color) -> bool
castleRights, enPassant, toMove
Game
start()
move(from, to, promotion?) -> MoveResult
legalMoves() -> [Move]
resign(color)
status() -> READY | IN_PROGRESS | CHECK
| CHECKMATE | STALEMATE | RESIGNED
undo() // optional
Game.move in one breath:
move(from, to, promotion?):
if status is terminal: throw GAME_OVER
piece = board.at(from)
if piece is empty or piece.color != board.toMove:
throw NOT_YOUR_TURN
candidate = match among piece.pseudoLegalMoves(...)
if no candidate: throw ILLEGAL_MOVE
if pawn-to-last-rank and promotion missing: throw PROMOTION_REQUIRED
candidate.promotion = promotion
board.apply(candidate)
if board.inCheck(mover):
board.unapply(candidate)
throw ILLEGAL_MOVE // left own king in check
log.append(candidate)
board.toMove = opponent
status = derive(board) // check / mate / stale
return MoveResult(candidate, status)
Handlers do not poke board[rank][file]. Tests build a Board, hang two Piece instances, and call legalMoves. They do not subclass Game to stub a knight.
Factory: Piece.of(type, color) returns the strategy. Adding a fairy piece later is a new class, not a new case in move.
15. Tests that matter
Skip getters. Test the invariant: the side that just moved is never in check, and the opponent’s result is mate, stale, or a live position.
- Leave-in-check. Position from section 7.
move(b1, c3)→ILLEGAL_MOVE. Board unchanged. Status still Alice to move. - Castle through check. Bishop on a6, Alice O-O → rejected. Rights still present. King still on e1.
- Castle out of check. Rook on e8, Alice O-O → rejected even if f1 and g1 are empty and quiet.
- Pawn promotion. Pawn e7,
move(e7, e8, QUEEN)→ e8 is a White queen, e7 empty. Missing promotion →PROMOTION_REQUIRED. - En passant. Bob just played
f7-f5. Alicee5xf6e.p. → pawn on f6, f5 empty,enPassantcleared. - En passant expired. After
f7-f5, Alice plays some other legal move. Bob can no longer be taken e.p. on f6. - Checkmate. A known mate (back-rank, or queen on the hole).
legalMovesempty,inChecktrue, statusCHECKMATE. Furthermove→GAME_OVER. - Stalemate. King boxed, not attacked, zero legal moves. Status
STALEMATE, notCHECKMATE. - Pin. Bishop on the king’s file cannot step off it. Capturing the checking rook on that file is legal if it exists.
- Wrong turn / resign. Bob moves on White’s turn →
NOT_YOUR_TURN. Alice resigns inCHECK→RESIGNED. No more moves.
Tests (1) and (2) are the offer. If they only have time for two, run leave-in-check and castle-through-check.
16. What you will not write in 45 minutes
Say this out loud so they stop steering you into an engine.
Not an evaluator. No material tables, no piece-square scores, no “who is winning.” Mate and stale are rule outcomes, not search.
Not a search. Minimax, alpha-beta, quiescence, iterative deepening — that is how Stockfish chooses a move. This interview is whether Alice may make one.
Not bitboards. Pretty, fast, and a whiteboard tar pit. 8×8 plus rays is the Senior-sized generator.
Not a network product. No rooms, no WebSocket move stream, no Elo, no anti-cheat. If they want “online chess,” the room still calls this Game.move. The hard object does not change.
Not every FIDE draw. 50-move and threefold need a clock and a position hash. Name them. Implement them only if they ask and you have ten spare minutes.
Not a clock inside move. A Clock wrapping Game can flag Alice on timeout and jump to RESIGNED (or FLAG). Mixing seconds into isLegal is how people fail the castle tests.
Snake and Ladder is the same cut: rules and state, not a real-time platform. Parking lot is the same cut with a race instead of a filter.
17. Final architecture / object diagram
One game. Two callers. One filter.
Alice (White) Bob (Black)
| |
| move(from, to, promo?) / resign |
v v
+----------------------------------------------+
| Game |
| start / move / legalMoves / resign / undo |
| status, move log |
+-------------------+--------------------------+
|
v
+---------------+
| Board |
| 8×8 cells |
| toMove |
| castleRights |
| enPassant |
| apply/unapply|
| isAttacked |
+-------+-------+
|
+-----------------+------------------+
| | |
v v v
Piece Move Square
Knight from, to file, rank
Bishop flags
Rook captured
Queen promotion
King prev rights
Pawn
move your turn? -> match pseudo-legal -> apply -> own king safe?
no -> unapply, ILLEGAL
yes -> log, flip side, derive CHECK / MATE / STALE
Kafka, Redis, and a region pair are not in this picture. If those words appear, you left LLD.
Source of truth Board cells + castle rights + en passant + toMove
Derived status, in-check highlight, legal-move dots
Ephemeral UI arrows, optional clock display
Audit move log (undo / PGN)
18. Interview-ready summary
How to walk through in 10–15 minutes
0–2 min. Alice’s knight, Bob’s rook on the e-file. LLD, not an engine.
2–4 min. Standard chess, two players, no variants. Correctness over speed.
4–6 min. Entities: Square, Piece, Move, Board, Game. Rights and e.p. are state.
6–9 min. Naive switch in Game. Piece strategies emit pseudo-legal. Filter = own king safe.
9–12 min. Specials: double, e.p., castle (rights + path + not in/through/into), promotion.
12–15 min. Check / mate / stale, the state machine, two tests, what you will not write.
Key decisions to remember
- This is objects and a legality filter, not Stockfish and not a lobby.
- Pseudo-legal is geometry; legal is “after
apply, my king is not attacked.” - Pins, discovered check, and “must move out of check” fall out of that filter.
isAttackedmust not calllegalMoves— attacks ≠ legal moves (pawns, pins).- Castle rights are history flags, not “pieces look at home.”
- Castling needs empty path and not in / through / into check.
- En passant captures a pawn that is not on
to;unapplyhas to know that. - Promotion is part of the move, not a second API after the fact.
- Mate = in check + no legal move. Stale = not in check + no legal move.
- One
Gameper match. Ten thousand rooms is ten thousand of these, not Kafka.
Likely interviewer follow-up questions
- How do you know a move leaves the king in check?
- Why not put check logic on each piece?
- How do you castle? What if f1 is attacked?
- When is en passant legal, and what does
applydelete? - Pawn to the 8th with no promotion piece?
- Difference between checkmate and stalemate?
- How would you undo? What must a
Movestore? - How would you add a clock without breaking tests?
- What if they ask for an AI opponent?
- What if they ask for online multiplayer?
Senior-level points that differentiate the answer
- Name the illegal-in-check bug before drawing classes. Classes without a filter are a vocabulary list.
- Separate attacks from legal moves. That is how you avoid recursion and how pins still check.
- Treat castle rights and en passant as first-class state, not something you infer from the grid.
- Call out castle-through-check (f1) without being prompted.
apply/unapplyas the test seam; do not clone the whole board unless you say why (cloning is simpler and slower — fine here).- Refuse the engine and the WebSocket product in one sentence, then finish the rules.
A 1–2 minute verbal answer
Chess LLD is a board, pieces that generate pseudo-legal moves, and a game that only accepts a move if the mover’s king is safe afterward. I would not put a giant switch in
Game— promotion, en passant, and castling explode it. Each piece type owns its geometry. Sliding pieces walk rays; knights jump; pawns own the double-step, captures, en passant, and promotion; the king owns one-step moves and castle attempts. Castling also needs rights, an empty path, and no check on the from, through, or to squares. After I apply a candidate, I ask whether that side’s king is attacked. If yes, I unapply and reject. Check is “king square attacked.” Checkmate is check plus no legal reply. Stalemate is no check plus no legal reply. I would test leaving the king in check and castling through check. I would not write an evaluator, a search, or a multiplayer stack in this interview. A chess server is many of theseGameobjects behind a room id.
For HLD framing around this, see the System Design Interview Complete Guide and the questions hub.
