Why this matters: the design insight of this problem fits in one sentence — how a piece moves and whether the move is legal are different questions, and they belong to different layers. Candidates who fuse them re-implement king-safety logic six times, once per piece, and then meet a pinned bishop. Candidates who separate them get the entire rulebook's hardest clause implemented once, for free, everywhere.
The model in words
Say it before you draw it: "Each piece type knows its own movement pattern — that's polymorphism, one rule object per piece. But movement rules only produce candidates. A separate legality layer takes a candidate move, plays it on a scratch copy of the position, and asks one question: is the mover's king attacked now? If yes, illegal — no matter how correct the movement was."
That statement earns its keep with the interviewer because it names where the hard requirement from lesson 01 lands. King safety is not a per-piece concern — a knight move and a queen move expose a pinned king in exactly the same way — so putting it in the piece rules would be the same responsibility duplicated six times. One concern, one owner: that is the single responsibility principle, applied at the decision that matters most in this problem.
The pieces of the model
Position — the board state plus whose turn it is. An 8×8 grid of optional pieces is enough. The one operational demand on it: cheap to copy or cheap to apply-and-revert a move, because the legality layer simulates on it constantly.
Move — a value object: from-square, to-square. It carries no logic; it is the thing being judged.
MovementRules, one per piece type — each answers a narrow question: ignoring king safety, could this piece make this move on this board? The knight checks its L-shapes; the rook walks its rays and stops at blockers; the pawn is the fussy one (direction, initial double-step, diagonal-only captures). These are honest little state machines with no view of the bigger game.
The legality layer — the module the UI actually calls:
isLegal(position, move): 1. a piece of the mover's color sits on move.from (turn rule) 2. that piece's MovementRules accept the move (candidate) 3. simulate: play the move on a scratch position 4. the mover's king is NOT attacked in the result (king safety) legal only if all four hold
Step 4 needs one supporting query — isSquareAttacked(position, square, byColor) — and here is the design's second elegance: attack detection reuses the movement rules. "Is this square attacked by white?" is just "could any white piece pseudo-legally move here?" The rules you wrote for step 2 answer step 4. Nothing is implemented twice.
What was rejected, and why
The giant switch. One validate(move) with a switch on piece type, king-safety checks stitched into each branch. It works for a demo and then decays: every rule change touches the same growing function, and king safety gets six slightly different implementations — the classic sign that a responsibility has no owner.
Movement rules that "know about check." Giving each piece's rules access to king safety couples every piece to the game's global state. The pin — a piece that moves correctly but exposes its king — is precisely the case that breaks it: no local rule can see it, because pin-ness is a property of the position after the move, not of the movement pattern.
Mutating the real board during validation. Simulation on the live position with a manual "undo" is tempting and bug-prone — a missed revert corrupts the game. Either copy the position (simple, fine at interactive rates) or implement apply/revert as a disciplined pair (faster, more care). Choosing and saying the trade-off out loud is the design act; lesson 03 implements the copy first and shows the revert shape.
Invariants worth stating
- The real position is never mutated by validation — only scratch copies.
- Every legality verdict passes through the same four steps; there is no piece-specific bypass.
- Movement rules are pure functions of (position, move) — no hidden game state.
State these in the round. Interviewers grade candidates who can say what must always be true of their design, because that is the sentence that survives requirement changes.
Key takeaway
The chess validator's core design is one separation: per-piece movement rules produce candidates; a single legality layer simulates each candidate and asks whether the mover's king is attacked. King safety is implemented once — not per piece — attack detection reuses the movement rules, and the real board is never touched during validation. Movement is not legality; that sentence is the design.