Skip to content
System Design

Learn

Design Chess (LLD)

Low level design of a chess game: the piece hierarchy and polymorphic move validation, two-phase legality checking for check, and modelling castling, en passant and promotion.

4 min readUpdated 2 Sept 2026

#LLD#OOP#Polymorphism#Machine Coding

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 bot

Polymorphic movement

The obvious design, and the right one: each piece knows its own geometry.

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

java
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  → draw

Both 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 Move as 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.

Related