Skip to content
System Design

Learn

Design Snake and Ladder (LLD)

Low level design of Snake and Ladder: modelling jumps as one abstraction, the O(1) board lookup, turn management with a queue, and validating board configuration.

3 min readUpdated 2 Sept 2026

#LLD#OOP#Game Design#Machine Coding

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 position

Four small classes. Resist adding more — an interviewer will not reward a Cell class that holds only an integer.

The one abstraction

java
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

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

java
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, a LoadedDice for 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.

Related