Skip to the solution
HardFrontend13 min read

Build Minesweeper

339 views

The problem

Question presented to candidate: "Build Minesweeper. A 9 by 9 board hides 10 mines. Clicking a cell reveals it: a mine ends the game, a number shows how many of its eight neighbours are mines, and an empty cell opens up all the connected empty area around it. Right-click places a flag. The first click must never be a mine."

Requirements:

  • 9 by 9 board, 10 mines, a mines-left counter (mines minus flags) and a New game button.
  • First click is always safe and opens an area: mines are placed after it, avoiding that cell and its neighbours.
  • Revealing an empty (zero) cell opens the connected empty region and its numbered border.
  • Right-click, the F key, or a Flag mode toggle (for touch) flags and unflags; flagged cells cannot be revealed.
  • Hitting a mine shows all mines and stops the game; opening every safe cell wins.
  • Arrow keys move between cells; cells are labelled for screen readers.

What a strong answer should cover:

  • A cell model (mine, adj, open, flag) and rules as pure functions (reveal, toggleFlag) with a seeded RNG for mine placement.
  • Deferred, first-click-safe placement with a (partial) Fisher-Yates over the allowed cells, then neighbour counts.
  • An iterative flood fill with an explicit stack or queue, marking cells when they are pushed, and why recursion overflows the call stack on large boards.
  • Win detection by counting opened safe cells, not by rescanning the board each time.
  • Immutable updates that copy only the cells that changed, plus keyboard and touch alternatives to right-click.

Clarifying questions expected:

  • "Should the first click be safe on its own, or should it always open an area (its neighbours safe too)?"
  • "Do you want chording (clicking a number whose flags are all placed opens its other neighbours) and a timer?"
  • "Do we need custom board sizes?"

Code / implementation expected: Yes. A playable board in the framework of your choice, with reveal and flood fill in plain functions.

  • Games
  • Recursion
  • Grid

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

13 min read ยท 9 sections

React solution

Every file of the working solution. Open it in the playground to run and change it.

ReactComplete solution
import { useRef, useState } from "react";
import Cell from "./src/components/Cell";
import { createGame, flagsUsed, reveal, toggleFlag } from "./src/utils/minesweeper";

const MESSAGE = {
  ready: "Click any cell to start",
  playing: "Playing",
  won: "You win! ๐ŸŽ‰",
  lost: "Boom! You hit a mine.",
};

export default function App() {
  const [game, setGame] = useState(() => createGame(1));
  const [flagMode, setFlagMode] = useState(false);
  const [active, setActive] = useState(0);
  const refs = useRef([]);
  const { rows, cols } = game;

  const onReveal = (i) =>
    setGame((g) => (flagMode ? toggleFlag(g, i) : reveal(g, i)));
  const onFlag = (i) => setGame((g) => toggleFlag(g, i));

  function onKeyDown(i, e) {
    if (e.key === "f" || e.key === "F") return onFlag(i);
    const r = Math.floor(i / cols),
      c = i % cols;
    const to = {
      ArrowUp: [r - 1, c],
      ArrowDown: [r + 1, c],
      ArrowLeft: [r, c - 1],
      ArrowRight: [r, c + 1],
    }[e.key];
    if (!to) return;
    e.preventDefault();
    const [nr, nc] = [
      Math.min(rows - 1, Math.max(0, to[0])),
      Math.min(cols - 1, Math.max(0, to[1])),
    ];
    setActive(nr * cols + nc);
    refs.current[nr * cols + nc].focus();
  }

  return (
    <main className="mines">
      <h1>Minesweeper</h1>
      <div className="bar">
        <span data-testid="counter">
          Mines left: {game.mines - flagsUsed(game)}
        </span>
        <button
          type="button"
          aria-pressed={flagMode}
          onClick={() => setFlagMode((m) => !m)}
        >
          ๐Ÿšฉ Flag mode
        </button>
        <button
          type="button"
          onClick={() => setGame((g) => createGame(g.seed + 1))}
        >
          New game
        </button>
      </div>
      <div
        className="board"
        role="group"
        aria-label={
          "Minefield, " + rows + " by " + cols + ", " + game.mines + " mines"
        }
      >
        {game.cells.map((cell, i) => (
          <Cell
            key={i}
            index={i}
            cols={cols}
            cell={cell}
            exploded={i === game.exploded}
            active={i === active}
            buttonRef={(el) => (refs.current[i] = el)}
            onReveal={onReveal}
            onFlag={onFlag}
            onKeyDown={onKeyDown}
            onFocus={setActive}
          />
        ))}
      </div>
      <p className="status" role="status" data-testid="status">
        {MESSAGE[game.status]}
      </p>
    </main>
  );
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 98 of 107 in Machine coding

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

Up nextHard

Build a Variable-Size Virtualized Feed (Dynamic Heights)

Continue