Prompt
Implement the win-detection logic for Tic-Tac-Toe.
Write a function checkWinner(board) that takes a flat array of 9 cells
(X, O, or null) and returns "X", "O", or null.
Winning combinations: 3 in a row, column, or diagonal.
Solution
The lesson here is rules as data. The eight winning lines are a lookup table, and checkWinner shrinks to "for each line, are all three cells the same non-null mark?" Compare that to the loop-free alternative — nested row/column/diagonal scans — which is more code, harder to audit, and hides an off-by-one in every direction. When a rule set is small and finite, enumerating it beats computing it.
Two details do the real work:
board[a] &&is the null guard. Without it, a line of three empty cells satisfiesnull === null === nulland the function declaresnullthe "winner" — which is falsy, so the bug hides until someone checkswinner !== null. Truthiness guards that happen to work are still worth calling out explicitly.- Flat array of 9, not a 3x3 matrix. The win table indexes directly into it,
board[i]maps one-to-one onto the 9 pressables, and updates are a singlemap—board.map((cell, idx) => idx === i ? player : cell)— keeping every move an immutable copy the component cansetState.
The natural follow-up is generalizing to N-in-a-row on an M×M board, where the table explodes and you do switch to directional scanning from the last move. Knowing where the table approach stops scaling is part of the answer.
Red flag: win detection inside the tap handler mixed with setState. Derive the winner from the board (checkWinner(board)) — stored winner flags desync the moment undo or replay arrives.
Say it: "Win lines are data, the check is one loop with a null guard, and the winner is always derived from the board — never stored beside it."