Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
This is a shortest-path problem on an unweighted graph, so the answer is BFS. Flatten the n×n board into squares 1…n² using boustrophedon (snake) numbering, where each square has up to 6 outgoing edges (a dice roll of 1–6) and snakes/ladders are forced jumps to another square. BFS from square 1 layer by layer; the first time you dequeue n² the layer count is the minimum number of moves. If you exhaust the queue without reaching n², return −1. O(n²) time and space.
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you recognise 'minimum number of moves on an unweighted graph' as BFS, can model squares as nodes with up-to-6 edges, and can correctly translate the boustrophedon (snake) numbering into (row, col) coordinates.
Where candidates slip
Using DFS (which finds *a* path, not the shortest), chaining snakes/ladders (a jump should be applied only once), or getting the boustrophedon row/column conversion wrong.
How to stand out
State 'unweighted ⇒ BFS' explicitly, isolate the coordinate conversion into a clean helper, mark visited at enqueue keyed by destination, and note that a snake/ladder is a single forced jump, never chained.
Choose
Compare the available approaches
Use constraints and trade-offs to select the route you can defend in an interview.
Interactive decision treeInteractiveChoose the right approachFirst decision previewFewest moves to reach square n²Do edges have equal cost?Yes (1 roll each)No / weightedExplore the full treeCollapse the tree
Learn & trace
Build each solution step by step
Move from the core idea to a visual trace, implementation, proof, and edge cases.
Approach 1 of 2 · Optimal
BFS Layer-by-Layer (Optimal)
1Hint 1Explore hintHide hint
Each square 1…n² is a node. From square x you can roll to x+1 … x+6, so each node has up to 6 outgoing edges, every edge costing one move.
2Hint 2Explore hintHide hint
Equal edge cost ⇒ BFS finds the fewest moves. Process the queue one layer at a time and the layer number is the move count.
3Hint 3Explore hintHide hint
The fiddly part is mapping a label back to (row, col): rows are numbered bottom-up, and alternate direction. Write a helper, and remember a snake/ladder is a single forced jump.
A · Core idea
Strip away the board-game dressing and this is the most standard shortest-path problem there is.
Why BFS and not DFS or Dijkstra. A 'move' is one dice roll, and every roll costs exactly the same — one. When all edges have equal weight, BFS explores the graph in concentric layers of increasing distance, so the first time it reaches a node it has reached it by a shortest path. DFS would find a path but not necessarily the shortest, and Dijkstra's priority queue is wasted machinery on a uniform-cost graph. So the moment you read 'least number of moves' over equal-cost edges, BFS is the answer.
Modeling the board as a graph. Each square x (1…n²) is a node. The legal moves from x are exactly the labels x+1 through min(x+6, n²) — the six faces of the die, clipped at the final square. That's the edge set; there's no adjacency list to build because the edges are implicit in the arithmetic.
The boustrophedon conversion is the only real work. Squares are numbered from the bottom-left, snaking upward: the bottom row goes left-to-right, the row above goes right-to-left, and so on. To read board[r][c] for a label we invert that numbering. Let q = (label-1) / n and r = (label-1) % n. The row from the top is n-1-q (because numbering starts at the bottom). Whether the column runs forward or backward depends on the parity of q: even rows-from-bottom go left-to-right (col = r), odd ones go right-to-left (col = n-1-r). Getting this parity wrong is the single most common bug, so isolate it in a helper and test it on a couple of labels.
Forced jumps, applied once. After computing the landing square next, peek at its board value. If it's -1, you simply stop there. Otherwise a snake or ladder forces you to board[r][c] — and that's the square you actually occupy. Crucially, you do not chain: even if the destination of a ladder itself holds another snake, you take exactly one jump per move. Mark that final destination visited and enqueue it, never the intermediate roll square.
Counting moves by layers. We process the queue one full layer at a time, incrementing moves after each layer drains. Because BFS reaches every node by its shortest distance, the layer index at which n² is first dequeued is precisely the minimum number of moves. If the queue empties first, n² is unreachable and we return -1.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
board = [[-1,-1],[-1,3]] (n = 2, n² = 4)Interactive diagram
BFS layers as move counts
moves=0Each BFS layer is one dice roll; the layer where n² appears is the answer.
1C · Code with teaching comments
D · Why it works and complexityO(n²) timeO(n²) spaceOpen the invariant and proofExplore the proofHide the proof
The hard part of this 'graph' problem isn't the graph algorithm — it's the indexing. Once you trust 'unweighted shortest path ⇒ BFS', the entire challenge collapses to one correct coordinate-conversion helper. Naming that split out loud ('BFS is trivial; let me get the boustrophedon math right') is exactly the structured thinking interviewers reward.
ComplexityO(n²) time, O(n²) space — There are n² squares; each is enqueued at most once (visited guard) and explores ≤ 6 neighbours, so the work is O(6·n²) = O(n²). The visited array and queue are O(n²) space.
E · Edge cases and pitfalls7n = 2, reachable in one roll — Square 1 can roll directly to n²=4 → answer 1.Explore edge casesHide edge cases
Edge cases handled
n = 2, reachable in one rollSquare 1 can roll directly to n²=4 → answer 1.board with self-defeating snakes (example 3)Goal never enters the queue; the loop ends and returns -1.ladder whose destination has another ladderOnly the first jump is taken — the second is NOT chained, per the rules.
Pitfalls
- Using DFS — finds a path, not the shortest one.
- Chaining snakes/ladders instead of applying exactly one forced jump per move.
- Marking visited by the rolled square instead of the final destination, allowing duplicates.
- Off-by-one in the boustrophedon row/column conversion (forgetting numbering starts at the bottom).
Approach 2 of 2
BFS with a Distance Array
1Hint 1Explore hintHide hint
Instead of processing whole layers, store dist[square] = fewest moves to reach it, initialised to -1 (unseen).
2Hint 2Explore hintHide hint
When you pop a square, its dist is final; set each unseen neighbour's dist to dist[current] + 1 and enqueue.
3Hint 3Explore hintHide hint
dist[n²] is the answer (still -1 if unreachable). The visited check is simply 'dist == -1'.
A · Core idea
This is the same BFS with different bookkeeping — instead of an explicit layer loop, each node carries its own distance.
Distance replaces the layer counter. We keep an array dist where dist[s] is the minimum moves to reach square s, initialised to -1 to mean 'not yet seen'. dist[1] = 0 seeds the start. When we dequeue square label, BFS guarantees dist[label] is already its shortest distance, so any newly discovered neighbour target gets dist[target] = dist[label] + 1.
The dist array is also the visited set. A square is unvisited exactly when its distance is still -1. Checking dist[target] == -1 before relaxing both prevents re-enqueueing and preserves the shortest distance already recorded — because in unweighted BFS the first assignment is always the smallest. This folds two data structures (visited + distance) into one.
Reading off the answer. We can return early the moment we set or pop n², but it's just as clean to run BFS to completion and return dist[n²] — which is -1 automatically if the goal was never reached. That uniform handling of the unreachable case is the small ergonomic win of this version.
Same forced-jump logic. The coordinate conversion and the single-jump snake/ladder rule are identical to the layer version; only the move-counting mechanism changes. Pick whichever you find less error-prone under pressure — many people prefer the distance array because it removes the inner 'for each node in this layer' loop and its off-by-one risks.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
board = [[-1,-1],[-1,3]] (n = 2)pop 1 (dist 0)
rolls 2,3,4 unseen → dist=1 each, enqueue
dist[2]=dist[3]=dist[4]=11C · Code with teaching comments
Reference implementation previewpublic int snakesAndLadders(int[][] board) { int n = board.length; int[] dist = new int[n * n + 1]; Arrays.fill(dist, -1); dist[1] = 0;Explore the full codeHide the full code
D · Why it works and complexityO(n²) timeO(n²) spaceOpen the invariant and proofExplore the proofHide the proof
Layer-counting and a distance array are two interchangeable ways to track BFS depth. The distance array shines when you need the distance to every node, not just the goal, and it elegantly returns -1 for unreachable targets with no special-casing.
ComplexityO(n²) time, O(n²) space — Same graph, same traversal: each square enqueued once, ≤ 6 neighbours each → O(n²) time. The dist array is O(n²) space and doubles as the visited marker.
E · Edge cases and pitfalls6unreachable goal — dist[n²] is never set, stays -1, returned at the end.Explore edge casesHide edge cases
Edge cases handled
unreachable goaldist[n²] is never set, stays -1, returned at the end.goal reachable directlydist[n²] set to 1 on the first pop → returned as 1.large n=20 board400 squares, ≤6 edges each — well within O(n²).
Pitfalls
- Initialising dist to 0 instead of -1 — then the start can't be distinguished from unseen squares.
- Updating dist for an already-seen square — corrupts the shortest distance.
- Reusing the layer-loop AND the dist array together, double-counting moves.
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewpublic int snakesAndLadders(int[][] board) { int n = board.length; boolean[] visited = new boolean[n * n + 1]; Queue<Integer> queue = new LinkedList<>(); queue.offer(1);Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Chaining snakes and ladders
A move is one dice roll plus at most one forced jump. If the landing square has a ladder, you take it once; you do NOT then follow a snake/ladder at that destination. Chaining computes wrong distances.
Explore the code comparisonHide the code comparison
Chaining snakes and ladders
A move is one dice roll plus at most one forced jump. If the landing square has a ladder, you take it once; you do NOT then follow a snake/ladder at that destination. Chaining computes wrong distances.
Don't write this
r, c = to_coord(nxt)
while board[r][c] != -1: # wrong: chains jumps
nxt = board[r][c]
r, c = to_coord(nxt)
target = nxtWrite this instead
r, c = to_coord(nxt)
dest = board[r][c]
target = nxt if dest == -1 else dest # single forced jump2Wrong boustrophedon row/column math
Numbering starts at the bottom-left and alternates direction. Forgetting that rows count up from the bottom, or that odd rows reverse, maps labels to the wrong cell and reads the wrong board value.
Explore the code comparisonHide the code comparison
Wrong boustrophedon row/column math
Numbering starts at the bottom-left and alternates direction. Forgetting that rows count up from the bottom, or that odd rows reverse, maps labels to the wrong cell and reads the wrong board value.
Don't write this
int row = label / n; // ignores bottom-up + 1-indexing
int col = label % n; // ignores alternating direction
int dest = board[row][col];Write this instead
int quotient = (label - 1) / n;
int remainder = (label - 1) % n;
int row = n - 1 - quotient;
int col = (quotient % 2 == 0) ? remainder : n - 1 - remainder;
int dest = board[row][col];3Using DFS for a shortest-path question
DFS dives down one branch and may reach the goal via a long path first. Only BFS, exploring by increasing distance, guarantees the minimum number of moves on an unweighted graph.
Explore the code comparisonHide the code comparison
Using DFS for a shortest-path question
DFS dives down one branch and may reach the goal via a long path first. Only BFS, exploring by increasing distance, guarantees the minimum number of moves on an unweighted graph.
Don't write this
def dfs(square, moves):
if square == n * n:
return moves # not necessarily minimal
# explore one neighbour deeply...Write this instead
queue = deque([1])
moves = 0
while queue:
for _ in range(len(queue)):
square = queue.popleft()
if square == n * n:
return moves # BFS ⇒ first arrival is shortest
moves += 1Revise
Carry the pattern into the interview
Pattern to remember
Shortest path on an unweighted implicit graph → BFS
Code pattern to memorize
queue = [1]; visited = {1}; moves = 0
while queue:
for each node in this layer:
if node == n*n: return moves
for next in node+1 .. node+6:
dest = board value if snake/ladder else next
if dest unseen: visit + enqueue
moves += 1
return -1Rules to remember
- 1Each move = a dice roll, so every edge has weight 1 → BFS gives the shortest path (not DFS, not Dijkstra).
- 2Flatten 2D → 1D with boustrophedon numbering: rows alternate left-to-right and right-to-left, starting bottom-left.
- 3From square
xyou can go tox+1 … x+6; a snake/ladder at the landing square is a forced jump (apply once, don't chain). - 4Mark a square visited when you enqueue it, keyed by its final destination, not the intermediate roll square.
- 5Process the queue one layer at a time and increment
movesper layer; the layer index is the distance.
Use this pattern when
- Fewest steps / minimum moves on an unweighted board or grid
- Shortest transformation where each step has equal cost
- Implicit graph: nodes are states, edges are the legal moves between them
- Board / tile games asking 'minimum dice rolls / jumps to finish'
Choose another pattern when
- Edges have different costs → Dijkstra, not plain BFS
- You need ALL paths or the longest path → DFS / DP, not BFS
- Negative weights appear → Bellman-Ford
Interview takeaway
Snakes and Ladders is BFS hiding behind a board game. The only real work is the index math to flatten the boustrophedon grid; once squares are numbered 1…n², it's the same shortest-path-on-unweighted-graph BFS as Word Ladder, Open the Lock, and Minimum Genetic Mutation.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternBFS for shortest path on an unweighted implicit graph. Squares are nodes, dice rolls and forced jumps are equal-cost edges. Same family as Word Ladder, Open the Lock, Minimum Genetic Mutation, and Shortest Path in Binary Matrix — the recurring tell is 'minimum number of moves/steps with equal cost'.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Word LadderSame BFS shortest-path idea where nodes are words and edges are one-letter changes.
- Open the LockComing soonBFS over lock states; each wheel turn is one unweighted edge, with 'deadends' as blocked nodes.
- Shortest Path in Binary MatrixComing soonBFS on a grid with 8-directional moves — minimum steps from corner to corner.
- Minimum Genetic MutationBFS over gene strings; each single-character mutation against a bank is one move.