Hello! Welcome to another Low-Level Design case study.
In our last lesson, we designed a hotel reservation system. The core challenge was managing shared inventory (rooms) over date ranges, which led us to focus on database schemas, transactions, and concurrency control mechanisms like pessimistic and optimistic locking.
Today, we're shifting from a data-centric problem to a behavior-centric one: designing a chess game. Your learning outcome for this lesson is to design a chess game, modeling piece movements, game rules, and state.
This classic LLD problem will challenge us to model a complex set of rules and behaviors using Object-Oriented principles. Instead of worrying about concurrent transactions, our focus will be on state management, encapsulation, and applying design patterns to create a flexible and maintainable structure.
Step 1: Understanding the Requirements
As with any design problem, we start by clarifying the scope. A chess game has well-defined rules, but it's crucial to state our assumptions explicitly.
🟢 Chess Full System Design + LLD | OOP Design + Coding Explained
Let's begin by reviewing the fundamental rules and objectives of chess to establish a baseline for our design. The video 'Chess Full System Design' by codeWithAryan provides a concise summary.
Please watch from 00:30 to 08:50. This section covers: The basic rules of chess (board, pieces, turns). The movement pattern for each piece. Special moves like castling and pawn promotion. Winning conditions (checkmate) and draws (stalemate). How to translate these rules into a concrete set of requirements for our system in an interview context.
Based on the video and standard chess rules, let's summarize the core requirements for our design:
- Game Board: A standard 8x8 board.
- Players: Two players, White and Black. White always moves first.
- Pieces: Each player starts with 16 pieces (1 King, 1 Queen, 2 Rooks, 2 Bishops, 2 Knights, 8 Pawns).
- Turns: Players alternate turns.
- Move Validation: The system must validate every move based on the rules for the specific piece.
- Game State: The system must track the current state of the game, including whose turn it is and game-ending conditions.
- Winning/Drawing: The system must detect check, checkmate, and stalemate conditions.
For a 45-minute LLD interview, implementing every special move (like en passant) might be out of scope, but our design should be extensible enough to accommodate them.
Step 2: Identifying Core Entities and Behaviors
With the requirements defined, we can start modeling the system. What are the "nouns" in our problem description?
Game: The main orchestrator.Player: The actors who make moves.Board: The playing area.Cell(orSquare,Box): An individual square on the board.Piece: The items that are moved.Move: An action taken by a player.
A key part of LLD is not just identifying entities, but also their behaviors and how they differ.
System Design of Chess: Low-Level Design
Let's watch a clip from Gaurav Sen's 'System Design of Chess' that does an excellent job of brainstorming the common and unique behaviors of each chess piece.
Watch from 23:07 to 30:31. Pay close attention to how he distinguishes between: Common behaviors: All pieces can move and capture. Special behaviors: Only pawns can be promoted, only the king can castle, etc. The key insight: Even the 'common' move behavior is implemented differently for each piece. This is a critical observation that will guide our design.
This brainstorming leads us to the central design challenge: How do we model the different movement rules for each piece in a clean, extensible way?
Step 3: The Strategy Pattern for Piece Movement
A naive approach would be to have a giant if/else or switch statement in a move method, checking the piece type and applying the correct logic. This violates the Open/Closed Principle; adding a new piece (e.g., for a fantasy chess variant) would require modifying this core logic.
A much better solution is the Strategy Pattern. This pattern allows us to define a family of algorithms, encapsulate each one, and make them interchangeable.
Our "algorithms" are the movement rules.
- Define a
MovementStrategyinterface: This interface will declare a method likegetPossibleMoves(). - Create concrete strategy classes: We'll implement this interface for each piece:
KingMovementStrategy,QueenMovementStrategy,PawnMovementStrategy, etc. Each class will contain the specific logic for that piece's movement. - Compose the strategy in the
Piececlass: ThePiececlass will hold a reference to aMovementStrategyobject. When a piece needs to move, it delegates the call to its strategy object.
This design is flexible, maintainable, and adheres to SOLID principles.
Building a Professional Chess Game: A Complete Low ...
The article 'Building a Professional Chess Game' provides an excellent implementation of the Strategy Pattern in Java for this exact problem.
Please read the section 'Strategy Pattern: Flexible Piece Movement'. It contains clear code examples for the MovementStrategy interface, a concrete KingMovementStrategy, and the Piece class that uses the strategy.
Here's the core idea in code:
The Strategy Interface:
public interface MovementStrategy {
List<Position> getPossibleMoves(Position currentPosition, Board board);
}
A Concrete Strategy:
public class KnightMovementStrategy implements MovementStrategy {
@Override
public List<Position> getPossibleMoves(Position currentPosition, Board board) {
// ... logic for L-shaped moves ...
}
}
The Piece Class (Context):
public abstract class Piece {
protected final PieceColor color;
private MovementStrategy movementStrategy; // Composition
public Piece(PieceColor color, MovementStrategy strategy) {
this.color = color;
this.movementStrategy = strategy; // Strategy is injected
}
// Delegate the call to the strategy object
public List<Position> getPossibleMoves(Position currentPosition, Board board) {
return movementStrategy.getPossibleMoves(currentPosition, board);
}
}
Test your understanding!
The pawn's movement is complex. It moves forward one square, but can move two on its first turn. It captures diagonally, not forward. How would you approach designing the PawnMovementStrategy class to handle these different rules?
Show answer
The PawnMovementStrategy would need access to more context than just the board layout.
- First Move: The
Piececlass would need ahasMovedboolean flag. ThePawnMovementStrategywould check this flag. If!hasMoved, it would add the two-square forward move as a possibility. - Captures: When generating possible moves, the strategy would need to check the two diagonal-forward squares. If a diagonal square contains an opponent's piece (
board.getCell(pos).hasEnemyPiece(myColor)), that square is a valid move. - Standard Move: The strategy would check the square directly in front of the pawn. If it's empty, that square is a valid move.
This shows that the getPossibleMoves method in the strategy needs access to both the Board state and potentially some state from the Piece itself (like hasMoved).
Step 4: Assembling the Full Class Structure
Now that we've solved the core movement problem, we can build out the full class structure. We'll use other design patterns to improve our design.
Boardas a Singleton: In any single game, there is only one board. Making theBoardclass a singleton ensures that there's a single, globally accessible instance.PieceFactory: To avoid littering our board setup code withnew King(...),new Pawn(...), we can use a Factory to centralize piece creation.PieceFactory.createPiece(PieceType.ROOK, PieceColor.WHITE)is much cleaner.
The following UML diagram gives a comprehensive view of how these classes and patterns fit together.

Let's watch a final clip that puts these pieces together and discusses the overall game flow.
🟢 Chess Full System Design + LLD | OOP Design + Coding Explained
The 'Chess Full System Design' video also covers the implementation of the Board (as a Singleton), Cells, and the main Game class.
Please watch from 22:56 to 32:02. This part of the video demonstrates: The Cell class and the Board class, which is composed of cells. The implementation of the Singleton pattern for the Board. How the Piece class is updated to use the MovementStrategy. The structure of the main Game class that manages players, turns, and the game loop.
Step 5: Managing the Game Flow and Rules
The Game class is the engine of our system. It's responsible for:
- Initialization: Creating the players and using a
BoardFactoryto set up the board with pieces in their starting positions. - Turn Management: Keeping track of the
currentPlayer. After a valid move, it switches the turn. - Executing Moves: This is the most complex piece of logic. A
makeMove(player, from, to)method would orchestrate the following checks:- Is it
player's turn? - Is there a piece at the
fromposition, and does it belong toplayer? - Is the move to
toin the piece's list ofgetPossibleMoves()? - Crucially: Does this move leave the player's own King in check? This is a non-trivial check. A common way to implement this is to:
a. Create a temporary, hypothetical copy of the board.
b. Make the move on the temporary board.
c. Check if the current player's king is under attack on this new board.
d. If it is, the move is illegal.
- Is it
- Updating State: If a move is legal, the
Gameclass updates theBoardstate, captures any pieces, logs theMove, and switches thecurrentPlayer. - Checking for Game End: After each move, the
Gameclass must check if the new board state results in a checkmate or stalemate, updating theGameStatusaccordingly.
The Move itself can be encapsulated in a class, which aligns with the Command Pattern. A Move object would store the startPosition, endPosition, pieceMoved, and pieceCaptured. This is extremely useful for keeping a game log or implementing an "undo" feature.
Conclusion
We have successfully designed a chess game, focusing on creating a flexible and extensible object-oriented model. This exercise highlighted a different set of challenges compared to our previous booking system designs.
Key Takeaways:
- Behavior over Data: The primary challenge was modeling complex and varied behaviors (piece movements), not managing concurrent data access.
- Strategy Pattern is Key: For components that share an action (
move) but implement it differently, the Strategy pattern is the ideal solution. It promotes clean, decoupled, and extensible code. - Other Core Patterns:
- Singleton: Useful for ensuring a single instance of a global object like the
Board. - Factory: Decouples object creation from client code, making initialization cleaner.
- Command: Encapsulating actions like
Moveinto objects allows for logging, queuing, and undo functionality.
- Singleton: Useful for ensuring a single instance of a global object like the
- Model Complex Rules Incrementally: The logic for move validation, especially checking for self-check, is complex. In an interview, break it down logically: first, check piece-specific rules (via Strategy), then check board rules (like blocked paths), and finally, check game-state rules (like not putting your own king in check).
In our next lesson, we will continue exploring state-driven design by tackling another classic LLD problem: designing a vending machine. This will reinforce the importance of managing state transitions and object interactions in a clean and predictable way.