Skip to main content

Tic Tac Toe

Difficulty: Core

Problem Statement​

Build 3Γ—3 game with alternating players, win detection, invalid move prevention, and reset.

Live Demo​

Try the running app below β€” this is the target behavior for the challenge.

Tic Tac Toe β€” Live Demo

Next: X

What Interviewers Expect​

  • Single board source of truth.
  • Win checks for rows/cols/diagonals.
  • Disable cells after game ends.

Step-by-Step Implementation​

Step 1 β€” Board representation​

Use array of 9 cells (null | X | O).

const [board, setBoard] = useState(Array(9).fill(null));

Step 2 β€” Move handler​

Reject occupied cells and post-win clicks.

if (board[i] || winner) return;

Step 3 β€” Winner derivation​

Compute winner from board with win-line checks.

const lines = [[0,1,2],[3,4,5],[6,7,8], ...];

Complete Implementation​

Copy-paste ready single-file solution. Use this as your reference after attempting the challenge yourself, or paste it into CodeSandbox / StackBlitz to run locally.

App.jsx
import React, {useMemo, useState} from 'react';

const WIN_LINES = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
];

function getWinner(board) {
for (const [a, b, c] of WIN_LINES) {
if (board[a] && board[a] === board[b] && board[a] === board[c]) {
return board[a];
}
}
return board.every(Boolean) ? 'Draw' : null;
}

export default function App() {
const [board, setBoard] = useState(Array(9).fill(null));
const [xTurn, setXTurn] = useState(true);
const winner = useMemo(() => getWinner(board), [board]);

const play = (index) => {
if (board[index] || winner) return;

setBoard((prev) =>
prev.map((cell, idx) => (idx === index ? (xTurn ? 'X' : 'O') : cell)),
);
setXTurn((turn) => !turn);
};

const reset = () => {
setBoard(Array(9).fill(null));
setXTurn(true);
};

return (
<div>
<p>{winner ? `Result: ${winner}` : `Next: ${xTurn ? 'X' : 'O'}`}</p>

<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(3, 56px)',
gap: 4,
}}
>
{board.map((cell, index) => (
<button
key={index}
type="button"
onClick={() => play(index)}
disabled={!!cell || !!winner}
style={{
width: 56,
height: 56,
fontSize: '1.25rem',
fontWeight: 700,
}}
>
{cell}
</button>
))}
</div>

<button type="button" style={{marginTop: '0.75rem'}} onClick={reset}>
Reset
</button>
</div>
);
}

Final Checklist​

  • UI works for primary flow
  • Edge cases handled (empty/disabled/loading where relevant)
  • State updates are immutable
  • Components are readable and reasonably split
  • You can explain trade-offs out loud in an interview

Next Challenge​

Continue to the next item in the sidebar when you're comfortable implementing this from scratch in 30–45 minutes.