Last Updated: January 22, 2026
Snake and Ladder is a classic turn-based board game played by two or more players on a grid, typically numbered from 1 to 100. Each player starts at cell 1 and takes turns rolling a dice to determine how many steps to move forward.
The game includes:
The first player to land exactly on the final cell (e.g., cell 100) is declared the winner.
Loading simulation...
In this chapter, we will explore the low-level design of a snake and ladder game in detail.
Lets start by clarifying the requirements:
Before starting the design, it's important to ask thoughtful questions to uncover hidden assumptions and better define the scope of the system.
Here is an example of how a conversation between the candidate and the interviewer might unfold:
Candidate: Should the game support a standard 10x10 board with 100 cells, or should the board size be configurable?
Interviewer: For this version, let’s stick with the standard 10x10 board.
Candidate: Should the number and positions of snakes and ladders be fixed, or should they be configurable?
Interviewer: They should be configurable. The board should allow us to define the number and positions of snakes and ladders at initialization.
Candidate: How many players should the game support? Should it be limited to two, or should we support multiple players?
Interviewer: The game should support multiple players—at least two, but potentially more. Player turns should rotate in order.
Candidate: How should dice rolls be handled? Should we simulate a dice roll in the code or take it as input?
Interviewer: Let’s simulate dice rolls using random number generation from 1 to 6. No need for user input for the roll itself.
Candidate: What should happen if a player rolls a 6? Should they get another turn?
Interviewer: Yes, if a player rolls a 6, they get an extra turn immediately.
Candidate: Should a player roll exact number to land on cell 100, or can they overshoot and still win?
Interviewer: A player must land exactly on 100 to win. If the roll takes them beyond 100, their turn is skipped.
Candidate: Can multiple players occupy the same square at the same time?
Interviewer: Yes, more than one player can land on the same square. There's no interaction or conflict when this happens—no "bumping" or penalties.
After gathering the details, we can summarize the key system requirements.
After the requirements are clear, lets identify the core entities/objects we will have in our system.
How do you go from a list of requirements to actual classes? The key is to look for nouns in the requirements that have distinct attributes or behaviors. Not every noun becomes a class, but this approach gives you a starting point.
Let's walk through our requirements and identify what needs to exist in our system.
The board is central to everything. We need something to represent it. This gives us our first entity: Board.
Unlike Tic-Tac-Toe where each cell holds a symbol, Snake and Ladder cells are just positions. The interesting part is what happens at certain positions. The Board needs to know which positions have snakes or ladders and where they lead.
We clearly need Snake and Ladder entities. Both have a start position and an end position. A snake's start (head) must be higher than its end (tail). A ladder's start (bottom) must be lower than its end (top).
Notice the similarity?
Both are "board entities" that transport a player from one position to another. This suggests an abstract base class BoardEntity that Snake and Ladder can inherit from. The base class holds the common logic (start and end positions), while subclasses enforce their specific validation rules.
Because snakes and ladders share structure but differ in behavior. A snake must have start > end. A ladder must have start < end. Inheritance lets us enforce these constraints in the subclass constructors while sharing the common code.
We need to represent players. Each player has a name and a current position on the board. This gives us the Player entity.
Players start at position 0 (off the board) and move toward 100. Their position changes after each dice roll, potentially modified by snakes or ladders.
We need a Dice entity to simulate random rolls. While we could just call Math.random() directly in the game logic, encapsulating dice behavior in its own class provides several benefits:
Something needs to coordinate the gameplay: roll dice, move players, check for snakes/ladders, handle the special "roll 6 for extra turn" rule, and detect when someone wins. This orchestrator is our Game entity.
The game also needs to track its current state. Is it still running? Has someone won? An enum GameStatus with values NOT_STARTED, RUNNING, and FINISHED captures all possibilities cleanly.
Notice how we derived each entity from a specific requirement. Don't just list classes. Explain why each one exists. If you can't justify an entity's existence, you probably don't need it.
Also notice we chose inheritance (BoardEntity → Snake/Ladder) over composition because snakes and ladders genuinely "are" board entities with shared behavior and specialized validation.
Here's how these entities relate to each other:
We've identified three types of entities:
Enums define fixed sets of values. GameStatus tracks the game lifecycle.
Data Classes primarily hold data with minimal behavior. Player tracks name and position. BoardEntity (and its subclasses) holds start and end positions.
Core Classes contain the main logic. Dice generates random rolls, Board manages position transitions, and Game orchestrates the entire gameplay loop.
With our entities identified, let's define their attributes, behaviors, and relationships.
Now that we know what entities we need, let's flesh out their details. For each class, we'll define what data it holds (attributes) and what it can do (methods). Then we'll look at how these classes connect to each other.
We'll work bottom-up: simple types first, then data containers, then the classes with real logic. This order makes sense because complex classes depend on simpler ones.
Enums define fixed sets of values that provide type safety and make code self-documenting.
GameStatusTracks where we are in the game lifecycle.
Three distinct states cover the game lifecycle. Unlike Tic-Tac-Toe, there's no DRAW state because Snake and Ladder always eventually produces a winner.
We use a simple three-state enum rather than embedding winner information in the status. The winner is tracked separately in the Game class. This keeps the enum simple and focused on lifecycle state.
Data classes are simple containers that hold data with minimal behavior.
PlayerEncapsulates all relevant information about a player
The Player starts at position 0, which represents "off the board" (before cell 1). The position is mutable because it changes after every move. The name is immutable since it never changes during gameplxxay.
BoardEntityAbstract base class for snakes and ladders.
We use inheritance here rather than a single class with a "type" field. Why? Because snakes and ladders have different validation rules. A snake must have start > end (you slide down). A ladder must have start < end (you climb up). Putting this validation in subclass constructors is cleaner than checking a type field everywhere.
SnakeRepresents a snake on the board.
When a player lands on the snake's head (start position), they slide down to the tail (end position). The constructor enforces that the head is always higher than the tail.
LadderRepresents a ladder on the board.
When a player lands on the ladder's bottom (start position), they climb up to the top (end position). The constructor enforces that the bottom is always lower than the top.
Core classes contain the actual game logic.
DiceA utility class responsible for simulating a dice roll.
Making the range configurable allows future extensions like:
BoardManages the game board and position transitions.
The Board doesn't store individual cells because we don't need to track cell contents. Instead, it maintains a map from snake/ladder start positions to their end positions. When a player lands on a position, we check if there's an entry in this map. If so, we return the mapped position; otherwise, we return the original position.
Using a Map for O(1) lookup is efficient. We could store separate lists of snakes and ladders, but the map simplifies the getFinalPosition() logic to a single lookup.
GameMain orchestrator that coordinates all game elements.
Key Design Principles:
How do these classes connect? Let's examine the relationship types we use.
Composition means one object owns another. When the owner is destroyed, the owned object is destroyed too.
Aggregation means one object contains other objects, but the contained objects can exist independently.
Inheritance defines a hierarchy between classes where subclasses are specialized versions of the parent.
You might notice some structural patterns emerging in our design. Let's make them explicit and justify why each pattern is appropriate here.
The Problem: Creating a Game requires multiple configuration steps: setting up the board with snakes and ladders, adding players, and configuring the dice. If we use a constructor with many parameters, it becomes hard to read and error-prone. What order do the parameters go in? Which ones are optional?
The Solution: The Builder pattern encapsulates the construction logic in a separate Builder class. Each configuration step returns the builder, allowing method chaining. The final build() call validates everything and creates the Game.
Why This Pattern: We could use a constructor with many parameters, but consider:
The Builder is an inner static class of Game. This keeps related code together and allows the builder to access Game's private constructor. The build() method validates that all required components are set before creating the Game.
The Problem: Snakes and ladders share the same structure (start and end positions) but have different validation rules. We don't want to duplicate the common code.
The Solution: The abstract BoardEntity class defines the common structure and behavior. Subclasses (Snake, Ladder) provide their specific validation in their constructors, which call the parent constructor after validation.
Why This Pattern: This is a classic use of inheritance for shared structure with specialized behavior:
We chose inheritance over composition here because snakes and ladders genuinely "are" board entities. They share not just data but also behavior (the board treats them identically when looking up position transitions).
The Problem: External code shouldn't need to understand the internals of Board, Dice, and Player management. They just want to start and play a game.
The Solution: The Game class acts as a facade, providing simple play() method that hides all the complexity of turn management, dice rolling, position updates, and win detection.
Why This Pattern: The Game class provides:
Now let's translate our design into working code. We'll build bottom-up: foundational types first, then data classes, then the classes with real logic. This order matters because each layer depends on the ones below it.
We start with the enum that tracks game state.
Three states cover the game lifecycle. The game starts NOT_STARTED, transitions to RUNNING when play begins, and ends as FINISHED when someone wins.
These classes primarily hold data with minimal logic.
Players start at position 0, which represents "off the board" (before cell 1). The name is immutable, but position changes throughout the game.
Now let's implement the BoardEntity hierarchy:
The abstract base class stores the common attributes. Both fields are final because a snake or ladder's position never changes during the game.
The Snake constructor enforces the rule that snakes go downward. If someone tries to create a snake where the head is below the tail, we fail immediately with a clear error message.
Similarly, the Ladder constructor enforces that ladders go upward. This "fail fast" validation catches configuration errors immediately.
The Dice encapsulates random number generation with a configurable range.
The formula Math.random() * (max - min + 1) + min generates a random integer in the inclusive range [min, max]. For a standard die, this returns values 1 through 6 with equal probability.
The Board manages the game surface and position transitions.
The Board converts the list of BoardEntity objects into a Map for O(1) position lookups. The getFinalPosition() method is elegant: if the position is in the map (snake head or ladder bottom), return the mapped value. Otherwise, return the original position. This single method handles both snakes and ladders uniformly.
This is where everything comes together. The Game orchestrates the entire gameplay loop.
Let's break down the key aspects of the Game class:
The play() method:
The takeTurn() method handles all the game rules:
takeTurn() for the extra turnThe Builder pattern:
this for method chainingbuild() validates all components are setThe following diagram illustrates what happens during a player's turn:
Let's see the system in action with a demo that sets up a game with snakes and ladders.
The demo creates:
The Builder pattern makes this setup clean and readable. Each configuration step is explicit.
In the object-oriented design of a Snake and Ladder game, which entity is responsible for storing the positions of all snakes and ladders?