Requirements and Scope
Functional Requirements
- Two players take turns moving pieces on an 8x8 board
- Each player starts with 16 pieces: 1 King, 1 Queen, 2 Rooks, 2 Bishops, 2 Knights, 8 Pawns
- Pieces move according to specific rules (e.g., Rook moves in straight lines, Knight in L-shape)
- Capturing an opponent's piece removes it from the board
- The game ends when a player is checkmated (king under attack with no escape)
- Support special moves: castling, en passant, pawn promotion
- Players can resign or agree to a draw
Non-Functional Requirements
- Modularity: Easy to add new piece types or rule variations
- Testability: Each component independently testable
- Extensibility: Support for timed games, AI opponents later
- Performance: Move validation must be O(1) or O(board-size)
Core Entities
| Entity | Description |
|---|---|
| Player | Two players, each controlling one side |
| Board | 8x8 grid holding piece positions |
| Piece | Abstract base for all chess pieces |
| Move | Represents a single move (source, destination, special flags) |
| Game | Orchestrates turns, validation, and game state |
Game States
WAITING_FOR_PLAYERS -> IN_PROGRESS -> CHECK -> CHECKMATE -> GAME_OVER
|-> STALEMATE -> GAME_OVER
|-> RESIGNATION -> GAME_OVER
|-> DRAW -> GAME_OVER
Board and Piece Design
Coordinate System
We use a simple (row, col) system where (0,0) is top-left (A8 in chess notation) and (7,7) is bottom-right (H1).
A B C D E F G H
8 [r][n][b][q][k][b][n][r] Row 0 - Black pieces
7 [p][p][p][p][p][p][p][p] Row 1 - Black pawns
6 [ ][ ][ ][ ][ ][ ][ ][ ] Row 2
5 [ ][ ][ ][ ][ ][ ][ ][ ] Row 3
4 [ ][ ][ ][ ][ ][ ][ ][ ] Row 4
3 [ ][ ][ ][ ][ ][ ][ ][ ] Row 5
2 [P][P][P][P][P][P][P][P] Row 6 - White pawns
1 [R][N][B][Q][K][B][N][R] Row 7 - White pieces
Piece Hierarchy
// Color enum
public enum Color {
WHITE, BLACK;
public Color opposite() {
return this == WHITE ? BLACK : WHITE;
}
}
// Position record for immutable coordinates
public record Position(int row, int col) {
public boolean isValid() {
return row >= 0 && row < 8 && col >= 0 && col < 8;
}
}
// Abstract Piece base class
public abstract class Piece {
protected final Color color;
protected boolean hasMoved;
public Piece(Color color) {
this.color = color;
this.hasMoved = false;
}
public Color getColor() { return color; }
public boolean hasMoved() { return hasMoved; }
public void markMoved() { this.hasMoved = true; }
// Each piece type implements its own movement rules
public abstract List<Position> getValidMoves(Position from, Board board);
// Check if a move to 'to' is valid (doesn't leave own king in check)
public boolean canMoveTo(Position from, Position to, Board board) {
return getValidMoves(from, board).contains(to)
&& !board.wouldLeaveKingInCheck(from, to, this.color);
}
public abstract char getSymbol();
}
Concrete Piece Implementations
public class King extends Piece {
public King(Color color) { super(color); }
@Override
public List<Position> getValidMoves(Position from, Board board) {
List<Position> moves = new ArrayList<>();
int[][] directions = {{-1,-1},{-1,0},{-1,1},{0,-1},{0,1},{1,-1},{1,0},{1,1}};
for (int[] d : directions) {
Position to = new Position(from.row() + d[0], from.col() + d[1]);
if (to.isValid() && board.isEmptyOrEnemy(to, color)) {
moves.add(to);
}
}
// Castling moves
if (!hasMoved) {
addCastlingMoves(from, board, moves);
}
return moves;
}
private void addCastlingMoves(Position from, Board board, List<Position> moves) {
// Kingside castling (O-O)
if (canCastle(from, board, true)) {
moves.add(new Position(from.row(), from.col() + 2));
}
// Queenside castling (O-O-O)
if (canCastle(from, board, false)) {
moves.add(new Position(from.row(), from.col() - 2));
}
}
private boolean canCastle(Position from, Board board, boolean kingside) {
int col = kingside ? 7 : 0;
Piece rook = board.getPiece(new Position(from.row(), col));
if (rook == null || rook.hasMoved() || !(rook instanceof Rook)) return false;
// Check squares between king and rook are empty
int step = kingside ? 1 : -1;
for (int c = from.col() + step; c != col; c += step) {
if (!board.isEmpty(new Position(from.row(), c))) return false;
}
// King must not be in check, and must not pass through check
if (board.isSquareAttacked(from, color.opposite())) return false;
for (int c = from.col() + step; c != from.col() + 2 * step; c += step) {
if (board.isSquareAttacked(new Position(from.row(), c), color.opposite())) return false;
}
return true;
}
@Override
public char getSymbol() { return color == Color.WHITE ? 'K' : 'k'; }
}
public class Queen extends Piece {
public Queen(Color color) { super(color); }
@Override
public List<Position> getValidMoves(Position from, Board board) {
List<Position> moves = new ArrayList<>();
// Queen combines Rook and Bishop movement
addLineMoves(from, board, moves, new int[][]{{-1,0},{1,0},{0,-1},{0,1}});
addLineMoves(from, board, moves, new int[][]{{-1,-1},{-1,1},{1,-1},{1,1}});
return moves;
}
private void addLineMoves(Position from, Board board, List<Position> moves, int[][] directions) {
for (int[] d : directions) {
int r = from.row() + d[0], c = from.col() + d[1];
while (new Position(r, c).isValid()) {
if (board.isEmpty(new Position(r, c))) {
moves.add(new Position(r, c));
} else if (board.isEnemy(new Position(r, c), color)) {
moves.add(new Position(r, c));
break;
} else {
break;
}
r += d[0];
c += d[1];
}
}
}
@Override
public char getSymbol() { return color == Color.WHITE ? 'Q' : 'q'; }
}
public class Rook extends Piece {
public Rook(Color color) { super(color); }
@Override
public List<Position> getValidMoves(Position from, Board board) {
List<Position> moves = new ArrayList<>();
addLineMoves(from, board, moves, new int[][]{{-1,0},{1,0},{0,-1},{0,1}});
return moves;
}
@Override
public char getSymbol() { return color == Color.WHITE ? 'R' : 'r'; }
}
public class Bishop extends Piece {
public Bishop(Color color) { super(color); }
@Override
public List<Position> getValidMoves(Position from, Board board) {
List<Position> moves = new ArrayList<>();
addLineMoves(from, board, moves, new int[][]{{-1,-1},{-1,1},{1,-1},{1,1}});
return moves;
}
@Override
public char getSymbol() { return color == Color.WHITE ? 'B' : 'b'; }
}
public class Knight extends Piece {
public Knight(Color color) { super(color); }
@Override
public List<Position> getValidMoves(Position from, Board board) {
List<Position> moves = new ArrayList<>();
int[][] jumps = {{-2,-1},{-2,1},{-1,-2},{-1,2},{1,-2},{1,2},{2,-1},{2,1}};
for (int[] j : jumps) {
Position to = new Position(from.row() + j[0], from.col() + j[1]);
if (to.isValid() && board.isEmptyOrEnemy(to, color)) {
moves.add(to);
}
}
return moves;
}
@Override
public char getSymbol() { return color == Color.WHITE ? 'N' : 'n'; }
}
public class Pawn extends Piece {
public Pawn(Color color) { super(color); }
@Override
public List<Position> getValidMoves(Position from, Board board) {
List<Position> moves = new ArrayList<>();
int direction = color == Color.WHITE ? -1 : 1;
int startRow = color == Color.WHITE ? 6 : 1;
// Forward one
Position oneAhead = new Position(from.row() + direction, from.col());
if (oneAhead.isValid() && board.isEmpty(oneAhead)) {
moves.add(oneAhead);
// Forward two from starting position
Position twoAhead = new Position(from.row() + 2 * direction, from.col());
if (from.row() == startRow && board.isEmpty(twoAhead)) {
moves.add(twoAhead);
}
}
// Diagonal captures
for (int dc : new int[]{-1, 1}) {
Position diag = new Position(from.row() + direction, from.col() + dc);
if (diag.isValid() && board.isEnemy(diag, color)) {
moves.add(diag);
}
}
// En passant
Position enPassant = board.getEnPassantTarget();
if (enPassant != null && enPassant.row() == from.row() + direction
&& Math.abs(enPassant.col() - from.col()) == 1) {
moves.add(enPassant);
}
return moves;
}
@Override
public char getSymbol() { return color == Color.WHITE ? 'P' : 'p'; }
}
Move Validation and Game Flow
Board Class
public class Board {
private final Piece[][] grid;
private Position enPassantTarget;
public Board() {
grid = new Piece[8][8];
initialize();
}
private void initialize() {
// Black pieces (row 0)
grid[0][0] = new Rook(Color.BLACK);
grid[0][1] = new Knight(Color.BLACK);
grid[0][2] = new Bishop(Color.BLACK);
grid[0][3] = new Queen(Color.BLACK);
grid[0][4] = new King(Color.BLACK);
grid[0][5] = new Bishop(Color.BLACK);
grid[0][6] = new Knight(Color.BLACK);
grid[0][7] = new Rook(Color.BLACK);
for (int c = 0; c < 8; c++) grid[1][c] = new Pawn(Color.BLACK);
// White pieces (row 7)
grid[7][0] = new Rook(Color.WHITE);
grid[7][1] = new Knight(Color.WHITE);
grid[7][2] = new Bishop(Color.WHITE);
grid[7][3] = new Queen(Color.WHITE);
grid[7][4] = new King(Color.WHITE);
grid[7][5] = new Bishop(Color.WHITE);
grid[7][6] = new Knight(Color.WHITE);
grid[7][7] = new Rook(Color.WHITE);
for (int c = 0; c < 8; c++) grid[6][c] = new Pawn(Color.WHITE);
}
public Piece getPiece(Position pos) {
return grid[pos.row()][pos.col()];
}
public void setPiece(Position pos, Piece piece) {
grid[pos.row()][pos.col()] = piece;
}
public boolean isEmpty(Position pos) {
return getPiece(pos) == null;
}
public boolean isEnemy(Position pos, Color myColor) {
Piece p = getPiece(pos);
return p != null && p.getColor() != myColor;
}
public boolean isEmptyOrEnemy(Position pos, Color myColor) {
return isEmpty(pos) || isEnemy(pos, myColor);
}
public Position getEnPassantTarget() { return enPassantTarget; }
public void setEnPassantTarget(Position pos) { this.enPassantTarget = pos; }
// Find king position for a given color
public Position findKing(Color color) {
for (int r = 0; r < 8; r++) {
for (int c = 0; c < 8; c++) {
Piece p = grid[r][c];
if (p instanceof King && p.getColor() == color) {
return new Position(r, c);
}
}
}
throw new IllegalStateException("King not found for " + color);
}
// Check if a square is attacked by any enemy piece
public boolean isSquareAttacked(Position pos, Color attackerColor) {
for (int r = 0; r < 8; r++) {
for (int c = 0; c < 8; c++) {
Piece p = grid[r][c];
if (p != null && p.getColor() == attackerColor) {
if (p.getValidMoves(new Position(r, c), this).contains(pos)) {
return true;
}
}
}
}
return false;
}
// Check if making a move would leave own king in check
public boolean wouldLeaveKingInCheck(Position from, Position to, Color myColor) {
// Simulate the move
Piece movedPiece = getPiece(from);
Piece captured = getPiece(to);
setPiece(to, movedPiece);
setPiece(from, null);
Position kingPos = findKing(myColor);
boolean inCheck = isSquareAttacked(kingPos, myColor.opposite());
// Undo the move
setPiece(from, movedPiece);
setPiece(to, captured);
return inCheck;
}
// Execute a move on the board
public void executeMove(Move move) {
Piece piece = getPiece(move.from());
Piece captured = getPiece(move.to());
// Handle castling rook movement
if (piece instanceof King && Math.abs(move.to().col() - move.from().col()) == 2) {
boolean kingside = move.to().col() > move.from().col();
int rookFromCol = kingside ? 7 : 0;
int rookToCol = kingside ? 5 : 3;
Piece rook = getPiece(new Position(move.from().row(), rookFromCol));
setPiece(new Position(move.from().row(), rookToCol), rook);
setPiece(new Position(move.from().row(), rookFromCol), null);
rook.markMoved();
}
// Handle en passant capture
if (piece instanceof Pawn && move.to().equals(enPassantTarget)) {
int capturedRow = move.from().row();
setPiece(new Position(capturedRow, move.to().col()), null);
}
// Set en passant target for next move
if (piece instanceof Pawn && Math.abs(move.to().row() - move.from().row()) == 2) {
enPassantTarget = new Position(
(move.from().row() + move.to().row()) / 2,
move.from().col()
);
} else {
enPassantTarget = null;
}
// Handle pawn promotion
if (piece instanceof Pawn && (move.to().row() == 0 || move.to().row() == 7)) {
piece = move.promotionPiece() != null ? move.promotionPiece() : new Queen(piece.getColor());
}
setPiece(move.to(), piece);
setPiece(move.from(), null);
piece.markMoved();
}
// Deep copy for simulation
public Board copy() {
Board copy = new Board();
for (int r = 0; r < 8; r++) {
for (int c = 0; c < 8; c++) {
copy.grid[r][c] = this.grid[r][c];
}
}
copy.enPassantTarget = this.enPassantTarget;
return copy;
}
}
Game Class
public record Move(Position from, Position to, Piece promotionPiece) {}
public enum GameStatus {
IN_PROGRESS, CHECK, CHECKMATE, STALEMATE, RESIGNATION, DRAW
}
public class Game {
private final Board board;
private final Player player1; // White
private final Player player2; // Black
private Color currentTurn;
private GameStatus status;
private final List<Move> moveHistory;
public Game(Player player1, Player player2) {
this.board = new Board();
this.player1 = player1;
this.player2 = player2;
this.currentTurn = Color.WHITE;
this.status = GameStatus.IN_PROGRESS;
this.moveHistory = new ArrayList<>();
}
public boolean makeMove(Position from, Position to, Piece promotionPiece) {
if (status != GameStatus.IN_PROGRESS && status != GameStatus.CHECK) {
return false; // Game is over
}
Piece piece = board.getPiece(from);
if (piece == null || piece.getColor() != currentTurn) {
return false; // Not the player's piece
}
// Validate move
if (!piece.canMoveTo(from, to, board)) {
return false;
}
// Execute move
Move move = new Move(from, to, promotionPiece);
board.executeMove(move);
moveHistory.add(move);
// Switch turns
currentTurn = currentTurn.opposite();
// Update game status
updateStatus();
return true;
}
private void updateStatus() {
Position kingPos = board.findKing(currentTurn);
boolean inCheck = board.isSquareAttacked(kingPos, currentTurn.opposite());
boolean hasLegalMoves = hasAnyLegalMove(currentTurn);
if (inCheck && !hasLegalMoves) {
status = GameStatus.CHECKMATE;
} else if (!inCheck && !hasLegalMoves) {
status = GameStatus.STALEMATE;
} else if (inCheck) {
status = GameStatus.CHECK;
} else {
status = GameStatus.IN_PROGRESS;
}
}
private boolean hasAnyLegalMove(Color color) {
for (int r = 0; r < 8; r++) {
for (int c = 0; c < 8; c++) {
Piece p = board.getPiece(new Position(r, c));
if (p != null && p.getColor() == color) {
Position from = new Position(r, c);
for (Position to : p.getValidMoves(from, board)) {
if (p.canMoveTo(from, to, board)) {
return true;
}
}
}
}
}
return false;
}
public void resign(Color playerColor) {
status = GameStatus.RESIGNATION;
}
}
Follow-ups and Extensions
Pawn Promotion Handler
public class PromotionHandler {
public static Piece promote(Pawn pawn, String choice) {
return switch (choice.toLowerCase()) {
case "queen" -> new Queen(pawn.getColor());
case "rook" -> new Rook(pawn.getColor());
case "bishop" -> new Bishop(pawn.getColor());
case "knight" -> new Knight(pawn.getColor());
default -> new Queen(pawn.getColor()); // Default to queen
};
}
}
Move History with Undo
public class MoveHistory {
private final Deque<GameState> history = new ArrayDeque<>();
public void save(GameState state) {
history.push(state);
}
public GameState undo() {
if (history.isEmpty()) return null;
return history.pop();
}
}
public record GameState(Board board, Color currentTurn, GameStatus status) {}
Timed Chess (Chess Clock)
public class ChessClock {
private final long whiteTimeMs;
private final long blackTimeMs;
private final long incrementMs;
private Color activeColor;
private long lastTickMs;
public ChessClock(long timeMs, long incrementMs) {
this.whiteTimeMs = timeMs;
this.blackTimeMs = timeMs;
this.incrementMs = incrementMs;
this.activeColor = Color.WHITE;
}
public void switchTurn() {
long now = System.currentTimeMillis();
long elapsed = now - lastTickMs;
// Deduct time and add increment
lastTickMs = now;
activeColor = activeColor.opposite();
}
public boolean isTimeUp(Color color) {
return getTimeRemaining(color) <= 0;
}
}
Observer Pattern for UI Updates
public interface GameObserver {
void onMoveMade(Move move, Piece captured);
void onCheck(Color kingColor);
void onCheckmate(Color loser);
void onGameOver(GameStatus status);
}
public class Game {
private final List<GameObserver> observers = new ArrayList<>();
public void addObserver(GameObserver observer) {
observers.add(observer);
}
private void notifyMove(Move move, Piece captured) {
for (GameObserver o : observers) o.onMoveMade(move, captured);
}
}
Design Patterns Used
| Pattern | Usage |
|---|---|
| Strategy | Each piece encapsulates its own movement validation |
| Composite | Board contains Pieces in a grid structure |
| Observer | UI components subscribe to game state changes |
| Command | Moves as objects enabling undo/redo |
| State | Game status transitions (IN_PROGRESS -> CHECK -> CHECKMATE) |
Complexity Analysis
| Operation | Time Complexity |
|---|---|
| Get valid moves for piece | O(n) where n = board size |
| Check if king in attack | O(n²) worst case |
| Execute move | O(1) |
| Check for checkmate | O(n² × n) = O(n³) |
| Find king position | O(n²) |
Interview Tips
- Start by defining the core entities and their relationships
- Discuss the Strategy pattern for piece movement early
- Mention special moves (castling, en passant, promotion) as follow-ups
- Be ready to discuss check/checkmate detection algorithms
- Consider thread safety if discussing multiplayer/networked games
- Mention how you would test each component independently
Practice Problems
Design a scalable Chess Game system. Cover high-level architecture, data model, and API design.
Solution
// Complete system design:
// - Functional + Non-functional requirements
// - Capacity estimation
// - Data model (SQL/NoSQL choice)
// - API endpoints
// - Component architecture
// - Scaling strategy
// - Monitoring & reliabilityHow would you scale Chess Game to handle 10x the current load? Identify bottlenecks and solutions.
Solution
// Scaling approach:
// 1. Load balancing
// 2. Database sharding/replication
// 3. Cache layer (Redis)
// 4. CDN for static assets
// 5. Async processing (queues)
// 6. Microservices decompositionAnalyze potential failure modes for Chess Game and design mitigation strategies.
Solution
// Failure mitigation:
// 1. Redundancy (multi-AZ)
// 2. Circuit breakers
// 3. Retry with backoff
// 4. Dead letter queues
// 5. Health checks
// 6. Graceful degradationQuiz
1. What design pattern is most appropriate for implementing different movement rules for each chess piece?
2. How do you detect checkmate?
3. What is en passant in chess?
4. In the code, why do we simulate moves when checking for check?
5. What is the time complexity of checking for checkmate?
Flashcards
Question
What is the Strategy Pattern and how is it used in Chess?
Click to reveal answer
Answer
Strategy Pattern defines a family of algorithms (piece movement rules) and makes them interchangeable. Each Piece subclass implements getValidMoves() differently, allowing the Board to validate moves polymorphically without knowing the specific piece type.
Question
How does castling work in code?
Click to reveal answer
Answer
Castling involves moving the King two squares toward a Rook, and the Rook jumping to the other side. Conditions: Neither piece has moved, squares between them are empty, King is not in check, and King doesn't pass through check. Implemented as a special case in King's getValidMoves().
Question
Why use a Board.copy() method?
Click to reveal answer
Answer
Board.copy() creates a deep copy for simulating moves without affecting the actual game state. This is essential for: 1) Checking if a move leaves own king in check, 2) AI move evaluation, 3) Undo functionality.
Question
What is the en passant target?
Click to reveal answer
Answer
The en passant target is set when a pawn moves two squares forward from its starting position. It stores the position the capturing pawn can move to (the square the captured pawn 'passed through'). It's cleared after one move if not used.
Question
How to handle pawn promotion?
Click to reveal answer
Answer
When a Pawn reaches the opposite end of the board (row 0 for White, row 7 for Black), it must be promoted to a Queen, Rook, Bishop, or Knight. In code, this is detected in executeMove() and the Pawn is replaced with the chosen piece type.
Revision Notes
Key Takeaways
- 1.Use Strategy pattern to encapsulate piece-specific movement rules
- 2.Always validate that moves don't leave your own king in check
- 3.Special moves (castling, en passant, promotion) are edge cases that need careful handling
- 4.Board simulation (copy + modify + check) is essential for move validation
- 5.Game state machine: IN_PROGRESS -> CHECK -> CHECKMATE/STALEMATE
- 6.Consider Observer pattern for UI updates and event handling
Interview Tips
- •Start with the core entities and their relationships before diving into logic
- •Explain the Strategy pattern for piece movement early in the discussion
- •Mention special moves as follow-ups to show depth of knowledge
- •Be prepared to discuss time complexity of check/checkmate detection
- •Consider thread safety if discussing multiplayer/networked extensions
- •Show how you would test each component independently
Cheat Sheet
Chess Game LLD Cheat Sheet
Core Entities: Player, Board, Piece (abstract), Move, Game
Piece Hierarchy:
Piece (abstract)
├── King - moves 1 square any direction, castling
├── Queen - combines Rook + Bishop movement
├── Rook - moves in straight lines (rows/cols)
├── Bishop - moves diagonally
├── Knight - L-shaped jumps (2+1)
└── Pawn - forward 1 (or 2 from start), diagonal capture
Key Design Patterns:
- Strategy: Each piece validates its own moves
- Observer: UI subscribes to game events
- Command: Moves as objects for undo/redo
- State: Game status transitions
Special Moves:
- Castling: King 2 squares + Rook jumps (conditions apply)
- En Passant: Pawn captures adjacent pawn that just moved 2 squares
- Promotion: Pawn reaches last rank, becomes Queen/Rook/Bishop/Knight
Check Detection: Simulate move -> check if king is attacked -> undo move
Checkmate: King in check + no legal moves exist
Stalemate: Not in check + no legal moves (draw)