A short machine-coding round with one insight at its centre: **a snake and a ladder are the same object.** Both move a piece from one cell to another. Modelling them as two classes with duplicated logic is the mistake this problem is designed to catch.
Clarify first
- Board size — always 100, or configurable N?
- Number of players and dice?
- Does landing exactly on the last cell matter, or is overshooting a win?
- Does rolling a six grant another turn?
- Can a jump land on the start of another jump — chained moves?
State your assumptions out loud, then design.
Entities
Game orchestrates turns and win detection
Board size + the jump lookup
Jump start → end (a snake if end < start, a ladder if end > start)
Dice roll(); count configurable
Player id, name, current positionFour small classes. Resist adding more — an interviewer will not reward a Cell class that
holds only an integer.
The one abstraction
record Jump(int start, int end) {
boolean isSnake() { return end < start; }
boolean isLadder() { return end > start; }
}
class Board {
private final int size;
private final Map<Integer, Integer> jumps = new HashMap<>(); // start → end
Board(int size, List<Jump> jumpList) {
this.size = size;
for (Jump j : jumpList) jumps.put(j.start(), j.end());
}
/** The final cell after applying any jump starting here. O(1). */
int resolve(int position) {
return jumps.getOrDefault(position, position);
}
}One map, one lookup, no branching on "is this a snake". Whether a jump goes up or down is data, not control flow. That is the whole design decision, and stating it explicitly is what earns the marks.
The game loop
class Game {
private final Board board;
private final Dice dice;
private final Deque<Player> players;
Player play() {
while (true) {
Player current = players.pollFirst();
int roll = dice.roll();
int next = current.position() + roll;
if (next > board.size()) {
players.addLast(current); // overshoot: forfeit the turn
continue;
}
next = board.resolve(next); // apply snake or ladder
current.moveTo(next);
if (next == board.size()) return current;
players.addLast(current);
}
}
}A deque handles turn rotation for any number of players. Note that the overshoot rule is a
policy decision you should have clarified — some variants allow overshooting to win, and
some bounce back off the final square.
Chained jumps
If a ladder can deposit you at the head of a snake, resolve must loop:
int resolve(int position) {
int seen = 0;
while (jumps.containsKey(position)) {
position = jumps.get(position);
if (++seen > jumps.size()) {
throw new IllegalStateException("cycle in board configuration");
}
}
return position;
}The guard matters: a badly configured board (5→20, 20→5) would otherwise loop forever. Detecting that is worth a sentence — most candidates do not.
Whether chaining is allowed is itself a rules question. Ask.
Validating the board
Cheap checks that show care, and a good thing to volunteer:
- No jump starts or ends outside
1..size. - No jump starts on cell 1 or on the final cell.
- No two jumps share a start cell — the map silently drops the duplicate otherwise.
- A snake's end is above 1; a ladder's end is below
size.
Patterns worth naming
- Strategy for dice behaviour — a standard
RandomDice, aLoadedDicefor testing, a
multi-dice variant. Injecting a deterministic dice is also what makes the game unit testable, which is a strong point to raise.
- Observer for move notifications to a UI or logger.
- Builder for constructing a board with many optional jumps.
- Factory for creating standard versus custom boards.
Edge cases to handle
- Overshooting the final cell.
- A jump landing exactly on the winning cell.
- Two players on the same cell — allowed in this game, unlike chess.
- A player already at the final cell.
- Zero jumps configured, or a board full of them.
Extensions to be ready for
- Multiple dice — sum them; the overshoot rule matters more.
- A six grants another turn — do not re-enqueue the player, but cap consecutive sixes.
- Persistence and replay — store the roll sequence rather than positions; it replays
deterministically because a seeded dice is a Strategy.
- Networked multiplayer — the loop becomes event-driven, awaiting each player's roll.
- Undo — a move stack of (player, from, to).
Track this problem on the System Design sheet.
Frequently asked
Should snakes and ladders be separate classes?
No. Both move a piece from one cell to another, so a single Jump entity with a start and an end covers both — a snake is simply a jump whose end is lower. Storing them in one start-to-end map makes the board lookup O(1) with no branching, and whether a jump goes up or down becomes data rather than control flow.
How do you handle a ladder that lands on a snake?
Make the board's resolve method loop until the position is no longer a jump start, with a counter that throws if it exceeds the number of jumps — otherwise a badly configured board like 5→20 and 20→5 loops forever. Whether chaining is allowed at all is a rules question worth clarifying before you design.
How do you make a dice game testable?
Inject the dice as a strategy. Production uses a random implementation; tests use a deterministic one that returns a scripted sequence, so specific scenarios — landing exactly on a snake head, overshooting the final cell — become repeatable unit tests instead of relying on chance.