Skip to the solution
HardMachine Coding12 min read

Build a Sudoku board with validation

511 views

The problem

Question presented to candidate: "Build a playable Sudoku board. Show a 9 by 9 grid with a puzzle prefilled; the given numbers cannot be edited. As the player types, highlight every cell that breaks the rules, meaning the same digit twice in a row, a column or a 3 by 3 box, and tell them when the puzzle is solved."

Requirements:

  • 81 cells; givens are visually distinct and read-only.
  • Only the digits 1 to 9 are accepted; typing over a cell replaces its digit; Backspace or Delete clears it.
  • Live validation: every cell involved in a duplicate in its row, column or box is highlighted, givens included.
  • A filled counter, a conflict counter and a "Solved" message when all 81 cells are filled with no conflicts.
  • Arrow keys move between cells; the row, column and box of the focused cell are softly highlighted.
  • A Clear button restores the original puzzle.

What a strong answer should cover:

  • One piece of state (81 numbers) with conflicts, counts and "solved" all derived from it, recomputed with memoisation.
  • An efficient conflict algorithm: walk the 27 units (9 rows, 9 columns, 9 boxes) and flag values that appear more than once, and the box index formula floor(row / 3) * 3 + floor(col / 3).
  • Input sanitising (letters, 0, paste) and keeping the DOM in sync when an edit is rejected.
  • Roving tabindex so the grid is one tab stop, with arrow-key navigation and labelled cells (aria-invalid on conflicts).
  • Validation versus solving: rule-checking does not need the solution; a solver (backtracking) is a separate concern.

Clarifying questions expected:

  • "Should we validate against the rules only, or against the known solution?"
  • "Do you want pencil marks (candidate notes) or just one digit per cell?"
  • "Where do puzzles come from: hard-coded, generated, or fetched?"

Code / implementation expected: Yes. A working grid with live conflict highlighting in the framework of your choice; the validation function should be plain and testable.

  • React
  • Grid
  • Validation

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

12 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 { useMemo, useRef, useState } from "react";
import Cell from "./src/components/Cell";
import {
  PUZZLE,
  digitFrom,
  findConflicts,
  isPeer,
  isSolved,
  moveFrom,
  parse,
  setCell,
} from "./src/utils/sudoku";

const START = parse(PUZZLE);
const GIVENS = START.map((v) => v > 0);

export default function App() {
  const [cells, setCells] = useState(START);
  const [active, setActive] = useState(0);
  const refs = useRef([]);

  // Derived on every render from the one piece of state that matters.
  const conflicts = useMemo(() => findConflicts(cells), [cells]);
  const filled = cells.filter((v) => v > 0).length;
  const solved = isSolved(cells, conflicts);

  function onKeyDown(i, e) {
    const j = moveFrom(i, e.key);
    if (j === -1) return;
    e.preventDefault();
    setActive(j);
    refs.current[j].focus();
  }

  return (
    <main className="sudoku">
      <h1>Sudoku</h1>
      <div className="bar">
        <span data-testid="filled">Filled: {filled}/81</span>
        <button type="button" onClick={() => setCells(START)}>
          Clear
        </button>
      </div>
      <div className="board" role="group" aria-label="Sudoku board, 9 by 9">
        {cells.map((v, i) => (
          <Cell
            key={i}
            index={i}
            value={v}
            given={GIVENS[i]}
            conflict={conflicts.has(i)}
            peer={isPeer(active, i)}
            active={active === i}
            inputRef={(el) => (refs.current[i] = el)}
            onValue={(idx, raw) =>
              setCells((c) => setCell(c, GIVENS, idx, digitFrom(raw)))
            }
            onKeyDown={onKeyDown}
            onFocus={setActive}
          />
        ))}
      </div>
      <p className="status" role="status" data-testid="status">
        {solved
          ? "Solved!"
          : conflicts.size
            ? conflicts.size + " cells in conflict"
            : "No conflicts"}
      </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 94 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 Whack-a-Mole

Continue