Back

React Tic-Tac-Toe Engine

medium

Streak

0 days

Progress

0%

Submitted

0

React Tic-Tac-Toe Engine

React30 minmediumFreeNew

Prompt

Tic-Tac-Toe is a deceptively simple game that makes for an excellent React interview question. Your task is to build a fully functional, two-player Tic-Tac-Toe game on a 3x3 grid. Player one is "X" and player two is "O", and they alternate turns clicking squares on the board. This challenge tests your ability to model game state correctly, derive values from a single source of truth, and structure your React components cleanly. A lot of candidates over-engineer this problem — the best solutions are remarkably simple. Your implementation should handle turn alternation, detect all win conditions (rows, columns, diagonals), recognize a draw, and let players restart the game at any time.

Requirements

  • →Render a 3x3 interactive game board
  • →Alternate turns between Player X and Player O
  • →Display current game status: whose turn it is, winner, or draw
  • →Detect all win conditions — rows, columns, and diagonals
  • →Prevent clicking on already-filled squares
  • →Provide a Restart button that resets the board to its initial state
Example
Loading preview...
For the best coding experience, we recommend using a desktop device.
Preparing Sandbox...
Premium interview report

What interviewers score in this build

Use this before reading the code. It tells you what to say, what to test, and where machine-coding candidates usually lose points.

Interview signals

  • React: Uses useState correctly, derives all secondary values (nextPlayer, status, winner) from a single squares array rather than maintaining redundant state.
  • HTML/CSS: Uses semantic button elements for squares (not divs), applies CSS Grid for the board layout, resets browser default button styles cleanly.
  • Component Architecture: Separates Game (logic + state), Board (renders grid), and Square (single cell) — each with a single clear responsibility.
  • State Management: Never mutates the squares array directly — always creates a copy before updating. Understands why immutability matters for React re-renders.

Time checkpoints

  1. 1

    0:00: Interview starts. Read the prompt carefully, note all requirements.

  2. 2

    3:00: Ask clarifying questions. Plan Game, Board, Square component structure.

  3. 3

    8:00: Scaffold all three components, initialize squares state with Array(9).fill(null).

  4. 4

    14:00: Implement click handler, turn alternation, and board rendering.

Edge-case checklist

Empty data and first-load state
Slow network, failed request, and retry path
Keyboard navigation and focus movement
Large input size, re-render pressure, and cleanup

Common mistakes

  • Starting with JSX before naming state and events.
  • Ignoring accessibility until the final minute.
  • Over-building abstractions instead of finishing the required behavior.
  • Failing to narrate trade-offs while coding.
SolutionRead-only · Live Preview

Technical Explanation

Problem Understanding

Let me walk you through this problem properly. Tic-Tac-Toe looks simple — it's a 3x3 grid where two players take turns. But it's a great interview question because it tests how you model state, structure components, and derive values cleanly in React.

Before writing a single line of code, ask these clarifying questions: Does clicking a filled square do anything? What happens after a win — can players keep clicking? Is there a score tracker across games, or just a single game with a restart? For this challenge, we keep it to a single game.

💡 Interview Tip: Taking 2 minutes to ask clarifying questions shows the interviewer you think before you code. It's one of the biggest differentiators between junior and senior candidates.

Component Architecture

Plan your component structure before writing any code. For Tic-Tac-Toe, the ideal breakdown is three components:

  • Game — The brain. Owns all state and game logic (whose turn it is, winner calculation, restart). This is a smart component.
  • Board — A dumb rendering component. Receives squares and onClick as props, renders the 3x3 grid. Knows nothing about game logic.
  • Square — A single button. Receives value and onClick, renders one cell.
⚠️ Common Mistake: Dumping everything into App.js. Candidates think creating separate components wastes time — in reality it takes 2 extra minutes and makes your code dramatically more readable. Interviewers notice this immediately.

State Design

Here is the key insight: you only need ONE piece of state — an array of 9 values representing the board squares.

const [squares, setSquares] = useState(Array(9).fill(null));

Everything else — whose turn it is, the winner, the game status — can be derived from this array. This is called derived state, and it's a critical React concept.

⚠️ Common Mistake: Creating separate state for currentPlayer, winner, and isGameOver. These go out of sync and create bugs. If something can be calculated from existing state, it should never be its own state variable.
// Derive current player — don't store it
const nextPlayer = squares.filter(Boolean).length % 2 === 0 ? 'X' : 'O';

Step-by-Step Implementation

Square Component

function Square({ value, onClick }) {
  return (
    <button className="square" onClick={onClick}>
      {value}
    </button>
  );
}
💡 Interview Tip: Always use a <button> element, not a <div>. Buttons are keyboard focusable, accessible, and handle Enter/Space key presses automatically. Using a div requires manually adding tabIndex, role="button", and keyboard handlers.

Board Component

function Board({ squares, onClick }) {
  return (
    <div className="board">
      {squares.map((square, i) => (
        <Square key={i} value={square} onClick={() => onClick(i)} />
      ))}
    </div>
  );
}

Game Component

export default function Game() {
  const [squares, setSquares] = useState(Array(9).fill(null));

  const nextPlayer = squares.filter(Boolean).length % 2 === 0 ? 'X' : 'O';
  const winner = calculateWinner(squares);
  const status = winner
    ? \`Winner: \${winner}\`
    : squares.every(Boolean) ? 'Match draw'
    : \`Next player: \${nextPlayer}\`;

  function handleClick(i) {
    if (squares[i] || winner) return;
    const next = [...squares];
    next[i] = nextPlayer;
    setSquares(next);
  }

  return (
    <main className="game">
      <p>{status}</p>
      <Board squares={squares} onClick={handleClick} />
      <button onClick={() => setSquares(Array(9).fill(null))}>Restart</button>
    </main>
  );
}

Core Logic

The winner calculation checks all 8 possible winning lines — 3 rows, 3 columns, 2 diagonals:

function calculateWinner(squares) {
  const lines = [
    [0,1,2],[3,4,5],[6,7,8], // rows
    [0,3,6],[1,4,7],[2,5,8], // columns
    [0,4,8],[2,4,6],          // diagonals
  ];
  for (const [a,b,c] of lines) {
    if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
      return squares[a];
    }
  }
  return null;
}

Edge Cases

  • Clicking a filled square: Guard with if (squares[i] || winner) return;
  • Moves after win: The same guard stops all moves once a winner exists
  • Draw detection: squares.every(Boolean) returns true when all 9 squares are filled

Styling Approach

Use CSS Grid for the board — it's the natural choice for a 2D layout:

.board {
  display: grid;
  grid-template-columns: repeat(3, 48px);
  grid-template-rows: repeat(3, 48px);
}
.square {
  border: 1px solid #999;
  background: #fff;
  font-size: 24px;
  font-weight: bold;
  margin: -1px 0 0 -1px; /* overlap borders to avoid double-width lines */
}
💡 Interview Tip: The negative margin trick (margin: -1px 0 0 -1px) makes all borders exactly 1px by overlapping adjacent borders. Without it, you get 2px borders between squares and 1px borders on edges — looks uneven.

Common Pitfalls

  • State mutation: Never do squares[i] = 'X' directly. Always spread: const next = [...squares]
  • Too much state: Storing currentPlayer, isGameOver, winner as separate states leads to sync bugs
  • Using divs instead of buttons: Fails accessibility checks and misses free keyboard support

How to Communicate During the Interview

Talk through your decisions as you code. Specifically mention:

  • "I'm representing the board as a flat array of 9 elements — simpler than a 2D array and maps directly to indices"
  • "I'm deriving nextPlayer from the squares array instead of adding a separate state — this eliminates an entire class of sync bugs"
  • "I'm using CSS Grid here because it's the natural tool for a grid layout — Flexbox would work but Grid is more semantic"
  • "I'm using a button element not a div because it gives us keyboard accessibility for free"

Interview Criteria

React

Uses useState correctly, derives all secondary values (nextPlayer, status, winner) from a single squares array rather than maintaining redundant state.

HTML/CSS

Uses semantic button elements for squares (not divs), applies CSS Grid for the board layout, resets browser default button styles cleanly.

Component Architecture

Separates Game (logic + state), Board (renders grid), and Square (single cell) — each with a single clear responsibility.

State Management

Never mutates the squares array directly — always creates a copy before updating. Understands why immutability matters for React re-renders.

Edge Cases

Guards against clicking a filled square, stops accepting moves after a winner is declared, handles the draw condition correctly.

Time Checkpoints

0:00

0:00: Interview starts. Read the prompt carefully, note all requirements.

3:00

3:00: Ask clarifying questions. Plan Game, Board, Square component structure.

8:00

8:00: Scaffold all three components, initialize squares state with Array(9).fill(null).

14:00

14:00: Implement click handler, turn alternation, and board rendering.

20:00

20:00: Add winner detection logic and game status display.

26:00

26:00: Add restart button, complete styling, handle all edge cases.

Streak

0 days

Last active: Sign in to track

Progress

0%

0/0 solved

Submitted

0

Solutions pushed to review history.