LLD CASE STUDIES / OBJECT DESIGN BRIEF

Design a chess game

Design a two-player chess game.

AdvancedPhase 09 / Topic 6 of 10ResponsibilitiesCollaborationsExtensibility
01

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.

A referee with a rule book

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.

02

When to use it

  • LLD interview for polymorphism and game state.
  • Practicing Command for moves and undo.
  • Rules engines with validation layers.
03

Where it shows up in interviews

Board games

Recognize it when: turns, pieces, rules, win conditions.

  • Design chess
  • Design tic-tac-toe
  • Design snakes and ladders
04

Where it is used in real software

Chess engines

Stockfish uses bitboards and move generation for speed, but the conceptual model is the same.

Lichess and chess.com

Store games in PGN notation: a log of moves that can be replayed.

Game servers

Authoritative servers validate every move to prevent cheating.

05

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.
06

How it works, step by step

  1. 1
    Clarify

    Two local players? Special moves? Timers? Undo?

  2. 2
    Entities

    Game, Board, Piece hierarchy, Position, Move, Player, GameStatus.

  3. 3
    Piece rules

    Each piece returns whether a move is geometrically valid and needs a clear path.

  4. 4
    Validation pipeline

    Turn, bounds, own-piece capture, piece rule, path clear, king safety.

  5. 5
    Status detection

    After each move: check, checkmate, stalemate.

07

Validating Nf3 from g1

White's turn, knight on g1, target f3 empty

Step 1 / 5
CheckResult
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.

08

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;    }}
09

Complexity and performance

Move validationO(64) for king safety

Scan attackers.

Checkmate detection (naive)O(64^2 x 64)

Fine for a board this small.

10

Trade-offs

Clarity vs speed

Object-per-piece designs are clear; engines use bitboards for millions of positions per second.

Where rules live

Piece geometry in pieces, global rules (check, castling) in the game or a rule validator.

11

Variants and related techniques

Special moves

Castling, en passant, and promotion as special Move subclasses or flags.

Online play

Server validates moves, clocks per player, spectators via Observer.

12

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.

13

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.

14

Practice problems

ProblemDifficultyWhat it trains
Tic-tac-toe with win detectionEasyGame loop.
Chess with check and checkmateHardValidation pipeline.