Back

React Tic-Tac-Toe Engine

medium

Streak

0 days

Progress

0%

Submitted

0

React Tic-Tac-Toe Engine

React30 minmediumFree

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

Click filled cell
Win then click
Draw
Reset

Common mistakes

  • Storing winner in state unnecessarily
  • Not blocking post-win moves
  • Mutating the board array
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.

More Machine Coding

View all Machine Coding →
Machine CodingMedium

Performant Image Slider

Machine CodingMedium

API Search and Render Image

Machine CodingEasy

Accessible React Tabs

Machine CodingMedium

Recursive File Explorer

Machine CodingEasy

Stopwatch

Machine CodingEasy

Tooltip

Related hubs

  • All machine coding
  • Frontend machine coding guide
  • React machine coding questions
  • UI coding interview questions
  • React UI interview questions
  • Google interview
  • Microsoft interview
  • Uber interview

FAQ

Why is Tic-Tac-Toe a common React machine coding prompt?

It is small enough to finish in one round but still forces board state, turn management, win detection, and immutable updates — all core React interview signals.

What edge cases should I handle?

Prevent moves on filled cells or after a winner, support reset, and keep the win/draw logic pure so it is easy to test and explain.

What related UI rounds should I practice next?

Tabs, accordion, modal system, and multi-step form — they reuse state modeling and interaction patterns from this challenge.