Overview
Design a two-player chess game. Requirements: an 8x8 board with standard pieces; players alternate turns; each piece moves by its own rules; moves are validated (inside the board, path clear, cannot capture own piece, cannot leave your king in check); the game detects check, checkmate, and stalemate; moves are recorded for history and undo.
The design uses polymorphism for pieces (King, Queen, Rook, Bishop, Knight, Pawn each implement move rules), a Board that owns Squares and piece positions, a Move object (Command) recording from, to, piece, and captured piece for undo, and a Game that manages turns and status. Special rules (castling, en passant, promotion) are good extension discussions.
Each piece knows how it may move, like players knowing their positions. The referee (game) checks turns, blocked paths, and whether a move would expose the king, and writes every move in the score sheet.
When to use it
- LLD interview for polymorphism and game state.
- Practicing Command for moves and undo.
- Rules engines with validation layers.
Where it shows up in interviews
Recognize it when: turns, pieces, rules, win conditions.
- Design chess
- Design tic-tac-toe
- Design snakes and ladders
Where it is used in real software
Stockfish uses bitboards and move generation for speed, but the conceptual model is the same.
Store games in PGN notation: a log of moves that can be replayed.
Authoritative servers validate every move to prevent cheating.
Key terms
- Piece
- Abstract class with color and move rules.
- Move
- From, to, piece, captured piece; supports undo.
- Pseudo-legal move
- Follows piece rules but may leave the king in check.
- Legal move
- Pseudo-legal and does not leave own king in check.
- Checkmate / stalemate
- No legal moves while in check / not in check.
How it works, step by step
- 1Clarify
Two local players? Special moves? Timers? Undo?
- 2Entities
Game, Board, Piece hierarchy, Position, Move, Player, GameStatus.
- 3Piece rules
Each piece returns whether a move is geometrically valid and needs a clear path.
- 4Validation pipeline
Turn, bounds, own-piece capture, piece rule, path clear, king safety.
- 5Status detection
After each move: check, checkmate, stalemate.
Validating Nf3 from g1
White's turn, knight on g1, target f3 empty
| Check | Result |
|---|---|
| Is it white's turn and a white piece? | Yes |
| Target inside the board? | Yes |
| Knight L-shape (2,1)? | Yes: dr=2, dc=1 |
| Path clear? (knights jump) | Not required |
| Own king left in check? | No: simulate move, test attacks |
NOWCheck: Is it white's turn and a white piece? | Result: Yes
Move validation is a pipeline; piece-specific geometry is delegated polymorphically.
Implementation
import java.util.*; enum Color { WHITE, BLACK; Color other() { return this == WHITE ? BLACK : WHITE; } }record Pos(int r, int c) { boolean inside() { return r >= 0 && r < 8 && c >= 0 && c < 8; } } abstract class Piece { final Color color; Piece(Color color) { this.color = color; } /** Geometry only; board checks occupancy and path. */ abstract boolean canMove(Pos from, Pos to, Board board); protected static boolean clearPath(Pos from, Pos to, Board b) { int dr = Integer.signum(to.r() - from.r()), dc = Integer.signum(to.c() - from.c()); for (Pos p = new Pos(from.r() + dr, from.c() + dc); !p.equals(to); p = new Pos(p.r() + dr, p.c() + dc)) { if (b.at(p) != null) return false; } return true; }} final class Rook extends Piece { Rook(Color c) { super(c); } boolean canMove(Pos f, Pos t, Board b) { return (f.r() == t.r() || f.c() == t.c()) && clearPath(f, t, b); }}final class Bishop extends Piece { Bishop(Color c) { super(c); } boolean canMove(Pos f, Pos t, Board b) { return Math.abs(f.r() - t.r()) == Math.abs(f.c() - t.c()) && clearPath(f, t, b); }}final class Queen extends Piece { Queen(Color c) { super(c); } boolean canMove(Pos f, Pos t, Board b) { return new Rook(color).canMove(f, t, b) || new Bishop(color).canMove(f, t, b); }}final class Knight extends Piece { Knight(Color c) { super(c); } boolean canMove(Pos f, Pos t, Board b) { int dr = Math.abs(f.r() - t.r()), dc = Math.abs(f.c() - t.c()); return dr * dc == 2; }}final class King extends Piece { King(Color c) { super(c); } boolean canMove(Pos f, Pos t, Board b) { return Math.max(Math.abs(f.r() - t.r()), Math.abs(f.c() - t.c())) == 1; }}final class Pawn extends Piece { Pawn(Color c) { super(c); } boolean canMove(Pos f, Pos t, Board b) { int dir = color == Color.WHITE ? 1 : -1, start = color == Color.WHITE ? 1 : 6; int dr = t.r() - f.r(), dc = Math.abs(t.c() - f.c()); Piece target = b.at(t); if (dc == 0 && target == null) return dr == dir || (f.r() == start && dr == 2 * dir && b.at(new Pos(f.r() + dir, f.c())) == null); return dc == 1 && dr == dir && target != null; // diagonal capture }} record Move(Pos from, Pos to, Piece piece, Piece captured) {} final class Board { private final Piece[][] grid = new Piece[8][8]; Piece at(Pos p) { return grid[p.r()][p.c()]; } void put(Pos p, Piece piece) { grid[p.r()][p.c()] = piece; } Move apply(Pos from, Pos to) { Move m = new Move(from, to, at(from), at(to)); put(to, m.piece()); put(from, null); return m; } void undo(Move m) { put(m.from(), m.piece()); put(m.to(), m.captured()); } Pos kingOf(Color c) { for (int r = 0; r < 8; r++) for (int col = 0; col < 8; col++) { Piece p = grid[r][col]; if (p instanceof King && p.color == c) return new Pos(r, col); } throw new IllegalStateException("No king"); } boolean attacked(Pos target, Color by) { for (int r = 0; r < 8; r++) for (int c = 0; c < 8; c++) { Piece p = grid[r][c]; if (p != null && p.color == by && p.canMove(new Pos(r, c), target, this)) return true; } return false; } static Board standard() { Board b = new Board(); for (Color color : Color.values()) { int back = color == Color.WHITE ? 0 : 7, pawns = color == Color.WHITE ? 1 : 6; Piece[] row = { new Rook(color), new Knight(color), new Bishop(color), new Queen(color), new King(color), new Bishop(color), new Knight(color), new Rook(color) }; for (int c = 0; c < 8; c++) { b.put(new Pos(back, c), row[c]); b.put(new Pos(pawns, c), new Pawn(color)); } } return b; }} final class Game { enum Status { ACTIVE, CHECK, CHECKMATE, STALEMATE } private final Board board = Board.standard(); private final Deque<Move> history = new ArrayDeque<>(); private Color turn = Color.WHITE; private Status status = Status.ACTIVE; Status move(Pos from, Pos to) { if (status == Status.CHECKMATE || status == Status.STALEMATE) throw new IllegalStateException("Game over"); if (!isLegal(from, to, turn)) throw new IllegalArgumentException("Illegal move"); history.push(board.apply(from, to)); turn = turn.other(); status = evaluate(turn); return status; } void undo() { if (!history.isEmpty()) { board.undo(history.pop()); turn = turn.other(); status = evaluate(turn); } } private boolean isLegal(Pos from, Pos to, Color side) { if (!from.inside() || !to.inside() || from.equals(to)) return false; Piece p = board.at(from); if (p == null || p.color != side) return false; Piece target = board.at(to); if (target != null && target.color == side) return false; if (!p.canMove(from, to, board)) return false; Move m = board.apply(from, to); // simulate boolean kingSafe = !board.attacked(board.kingOf(side), side.other()); board.undo(m); return kingSafe; } private Status evaluate(Color side) { boolean inCheck = board.attacked(board.kingOf(side), side.other()); boolean anyLegal = false; outer: for (int r = 0; r < 8; r++) for (int c = 0; c < 8; c++) for (int r2 = 0; r2 < 8; r2++) for (int c2 = 0; c2 < 8; c2++) { if (isLegal(new Pos(r, c), new Pos(r2, c2), side)) { anyLegal = true; break outer; } } if (!anyLegal) return inCheck ? Status.CHECKMATE : Status.STALEMATE; return inCheck ? Status.CHECK : Status.ACTIVE; }}Complexity and performance
Scan attackers.
Fine for a board this small.
Trade-offs
Object-per-piece designs are clear; engines use bitboards for millions of positions per second.
Piece geometry in pieces, global rules (check, castling) in the game or a rule validator.
Variants and related techniques
Castling, en passant, and promotion as special Move subclasses or flags.
Server validates moves, clocks per player, spectators via Observer.
Common mistakes
- Board checking piece types with instanceof for movement.
Fix: Polymorphic canMove on each piece.
- Forgetting that a move cannot leave your own king in check.
Fix: Simulate the move and test king safety.
- No undo capability.
Fix: Record Move objects with captured pieces.
Interview questions
How do you validate a chess move?
Check turn and ownership, bounds, not capturing your own piece, the piece's movement rule (polymorphic canMove), path clearance for sliding pieces, then simulate the move and ensure your own king is not attacked.
How would you detect checkmate?
After a move, check whether the opponent's king is attacked and whether the opponent has any legal move. In check with no legal moves is checkmate; not in check with no legal moves is stalemate.
Practice problems
| Problem | Difficulty | What it trains |
|---|---|---|
| Tic-tac-toe with win detection | Easy | Game loop. |
| Chess with check and checkmate | Hard | Validation pipeline. |