The hardest of the standard machine-coding games, because the rules are genuinely intricate. Nobody expects a complete chess engine in 90 minutes. What is being assessed is the **piece hierarchy**, how you separate "can this piece move there" from "is that move legal", and whether your model can express castling and en passant at all.
Entities
Game players, turn order, status, move history
Board 8×8 of Cells; piece lookup; applying and undoing moves
Cell row, col, occupying piece (nullable)
Piece abstract: colour, hasMoved, canMove(board, from, to)
├─ King, Queen, Rook, Bishop, Knight, Pawn
Move from, to, piece, captured, promotion, isCastling, isEnPassant
Player colour, human or botPolymorphic movement
The obvious design, and the right one: each piece knows its own geometry.
abstract class Piece {
protected final Colour colour;
protected boolean hasMoved = false;
/** Geometry only: can this piece reach 'to' on an empty-ish board?
Does NOT consider check — that is the Game's concern. */
abstract boolean canMove(Board board, Cell from, Cell to);
}
class Knight extends Piece {
boolean canMove(Board board, Cell from, Cell to) {
int dr = Math.abs(from.row() - to.row());
int dc = Math.abs(from.col() - to.col());
return dr * dc == 2 // 1×2 or 2×1
&& !board.isOccupiedBy(to, colour); // no friendly fire
}
}
class Bishop extends Piece {
boolean canMove(Board board, Cell from, Cell to) {
return Math.abs(from.row() - to.row()) == Math.abs(from.col() - to.col())
&& board.isPathClear(from, to) // sliding piece
&& !board.isOccupiedBy(to, colour);
}
}dr * dc == 2 for the knight is a neat trick worth using — it captures both 1×2 and 2×1 in
one expression.
Two shared concerns belong on Board, not repeated in every piece: isPathClear for the
sliding pieces (rook, bishop, queen) and isOccupiedBy for the friendly-capture rule.
Queen is naturally Rook.canMove(...) || Bishop.canMove(...) — say so; composing rather
than duplicating is the point.
Two-phase validation
The structural decision the question is really testing. A move can be geometrically valid and still illegal, because it leaves your own king in check.
boolean isLegal(Move move) {
// Phase 1: geometry
if (!move.piece().canMove(board, move.from(), move.to())) return false;
// Phase 2: king safety — make, test, unmake
board.apply(move);
boolean selfCheck = isKingInCheck(move.piece().colour());
board.undo(move);
return !selfCheck;
}Make/test/unmake is the standard technique, and it is why Board.undo must exist from
the start rather than being bolted on. It also means Move has to record the captured
piece and the previous hasMoved flags — a move object that only holds from and to cannot
be undone.
Everything else falls out of this one primitive:
in check = the opponent attacks my king's square
checkmate = in check AND no legal move exists
stalemate = NOT in check AND no legal move exists → drawBoth terminal conditions are the same loop over all legal moves, differing only by whether the king is currently attacked. Recognising that they share an implementation is a good sign.
The special rules
These are what separate a real model from a sketch, and interviewers ask about them precisely because they break naive designs.
Castling — needs state, not just geometry: neither king nor rook has moved, the squares
between are empty, and the king is not in check nor passing through an attacked square.
The hasMoved flag on Piece exists for this. It is also a move that relocates two
pieces, so Move must be able to express that.
En passant — depends on the previous move, so the game must retain move history; a
board state alone is insufficient. That is a strong argument for Game owning a move list
rather than only a current position.
Promotion — a pawn reaching the last rank is replaced by a chosen piece, so Move
carries a promotion field and apply may substitute the object entirely.
Pawns generally are the most complex piece despite looking simplest: direction depends on
colour, a two-square first move, diagonal-only capture, en passant, and promotion. Say that — it shows you have thought about the model rather than the pieces you find easy.
Patterns worth naming
- Strategy — the piece hierarchy is polymorphic movement; also for human versus engine
players.
- Command — each
Moveas an object with execute/undo gives you undo, replay, PGN
export and the make/test/unmake check detection, all from one abstraction. This is the pattern to lead with here.
- Memento — snapshotting board state, an alternative to undo (simpler, more memory).
- Observer — UI updates, clocks, move logging.
- Factory — creating a piece for promotion or for setting up the board.
Edge cases to handle
- Moving into check; discovered check when moving a pinned piece.
- Stalemate distinguished from checkmate.
- Draws: fifty-move rule, threefold repetition (needs position hashing), insufficient
material.
- Castling through, out of, or into check — three separate prohibitions.
- Promotion to something other than a queen.
Extensions to be ready for
- A chess engine — minimax with alpha-beta pruning over the legal-move generator you
already have.
- Clocks — per-player timers with increments.
- Notation — PGN export from the move history, FEN for a position.
- Networked play — the game becomes a server-side session; the move list is the state to
transmit.
- Bitboards — 64-bit integers per piece type, the performance representation real
engines use; worth naming as the answer to "how would you make this fast".
Track this problem on the System Design sheet.
Frequently asked
How do you validate a chess move?
In two phases. First ask the piece whether the move is geometrically possible — its own movement rules, path clearance for sliding pieces, and no capture of a friendly piece. Then apply the move to the board, test whether your own king is attacked, and undo it. A move that passes geometry but leaves your king in check is illegal, and this make-test-unmake technique is why the board needs an undo operation from the start.
How do you model castling and en passant?
Castling needs a hasMoved flag on the king and rook, plus checks that the intervening squares are empty and that the king is neither in check nor passing through an attacked square — and the Move object must be able to relocate two pieces. En passant depends on the immediately preceding move, so the game must keep a move history; a board position alone cannot express it.
What is the difference between checkmate and stalemate in code?
They share one implementation: generate every legal move for the side to play, and if there are none, the game is over. If the king is currently attacked it is checkmate; if it is not, it is stalemate and the game is a draw. The only difference is the in-check test.