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.