Skip to the solution
HardFrontend15 min read

Build Tic-Tac-Toe II (N x N board, M in a row)

157 views

The problem

Question presented to candidate: "You have built the classic 3 by 3 Tic-Tac-Toe before. Now make it general: the board is N by N and you need M in a row to win, both chosen by the player. The win check has to stay fast on a big board, and I want a move history where I can jump back to any earlier position and play on from there."

Requirements:

  • Controls for the board size N (3 to 8 here) and the win length M (3 to N); changing either starts a new game.
  • X and O alternate; filled cells and finished games refuse moves.
  • After each move, detect a win of M in a row in any of the 4 directions using only the lines through that move, and highlight the winning cells; detect a draw when the board is full.
  • A move list with "jump to move k"; making a new move after jumping back discards the later moves.
  • Arrow-key navigation between cells.

What a strong answer should cover:

  • Why the 3 x 3 LINES table does not scale, and the last-move check: 4 directions, walk both ways, stop after M - 1 cells, O(M) per move.
  • Flat-array indexing (row * n + col) and bounds checks so a scan never wraps to the next row.
  • History as a list of moves plus a step pointer, with the board derived by replaying; truncating the future on a new move.
  • Clamping M when N shrinks, and resetting the game on any config change.
  • A roving tabindex grid: one cell in the tab order, arrows move focus, aria-disabled instead of disabled so filled cells stay reachable.

Clarifying questions expected:

  • "Should a line longer than M also count as a win, as in Gomoku freestyle?"
  • "Does changing N or M mid-game reset the game, or should it be blocked while a game is running?"
  • "Do you want undo of single moves, or full jump-to-any-move time travel?"

Code / implementation expected: Yes. A working component plus the win-check function written and explained; the interviewer will often ask for its complexity.

  • Games
  • Algorithms
  • 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

15 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 { useState } from "react";
import Board from "./src/components/Board";
import History from "./src/components/History";
import { replay, statusText } from "./src/utils/game";

const SIZES = [3, 4, 5, 6, 7, 8];

export default function App() {
  const [n, setN] = useState(5);
  const [m, setM] = useState(4);
  const [moves, setMoves] = useState([]); // cell index of every move, in order
  // how many of those moves are on the board
  const [step, setStep] = useState(0);

  const game = replay(n, m, moves, step); // derived, never stored
  const gameOver = Boolean(game.winner) || game.draw;

  function play(i) {
    if (gameOver || game.board[i]) return;
    // a new move drops the redo future
    const next = moves.slice(0, step).concat(i);
    setMoves(next);
    setStep(next.length);
  }

  function resize(size) {
    setN(size);
    setM((need) => Math.min(need, size));
    setMoves([]);
    setStep(0);
  }

  function changeNeed(need) {
    setM(need);
    setMoves([]);
    setStep(0);
  }

  return (
    <main className="ttt">
      <h1>Tic-Tac-Toe II</h1>
      <div className="config">
        <label>
          Board size
          <select
            data-testid="size"
            value={n}
            onChange={(e) => resize(Number(e.target.value))}
          >
            {SIZES.map((s) => (
              <option key={s} value={s}>
                {s + " x " + s}
              </option>
            ))}
          </select>
        </label>
        <label>
          In a row to win
          <select
            data-testid="need"
            value={m}
            onChange={(e) => changeNeed(Number(e.target.value))}
          >
            {SIZES.filter((s) => s <= n).map((s) => (
              <option key={s} value={s}>
                {s}
              </option>
            ))}
          </select>
        </label>
      </div>
      <p className="status" data-testid="status" aria-live="polite">
        {statusText(game)}
      </p>
      <Board
        key={n + "x" + m}
        n={n}
        m={m}
        board={game.board}
        winLine={game.winner ? game.winner.line : []}
        gameOver={gameOver}
        onPlay={play}
      />
      <History moves={moves} step={step} n={n} onJump={setStep} />
    </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 104 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 basic Rich Text Editor

Continue