Tic Tac Toe
A deceptively small prompt that interviewers like precisely because it is small - there is
nowhere to hide a design decision behind volume of code. The one twist worth building for:
the board size and the win condition should not be nailed to 3 and "3 in a row".
Requirements
Functional
- Two players take turns placing their symbol on an empty cell.
- After each move, the game reports whether that move won, drew the game, or play continues.
- The board size is configurable - not hardcoded to 3x3.
Non-functional
- Checking for a win after a move should not rescan the entire board - only the cells that could possibly be affected by the move just played.
- Supporting a different win condition (say, a 5x5 board where 4 in a row wins instead of
5) should mean adding a class, not editing
Game.
Design
Board only knows how to store and report symbols in a grid - it has no idea what
"winning" means. WinningStrategy owns that question entirely, which is what lets a
5x5-board-needs-4-in-a-row variant exist as a second implementation instead of an if
branch inside Game. Game itself just alternates players, forwards each move to
Board, and asks the strategy whether that move ended the game.
- 1The referee only ever talks to the game - never to the board or a strategy directly.
- 2The board stores the mark and rejects the move if the cell was already taken.
- 3The game hands the just-played cell to the strategy - it never inspects the grid itself.
- 4The strategy walks outward from that cell in four directions, reading symbols as it goes.
- 5If nobody won, the game hands the turn to the other player.
- 6WON, DRAW or IN_PROGRESS - the referee reacts, the game never prints anything itself.
The strategy only ever looks at the cell that was just played and walks outward from it in four directions (horizontal, vertical, two diagonals) - never the whole board. A win can only involve the most recent move, so there is nothing to gain by looking anywhere else.
Class diagram
Code
Design decisions
WinningStrategyis an interface, not a method onBoard.Boardanswering "did this move win?" would force it to also know about win-length rules, coupling storage to a policy that changes far more often than storage does. Split apart, a Gomoku-style variant is one newLineWinningStrategy(winLength=4)instance, not a rewrite.- The win check scans outward from the last move instead of the whole board. A full
board scan is
O(n^2)per move and gets slower as the board grows; scanning four directions from one cell isO(n)regardless of board size, because a win can only ever include the cell that was just played. BoardexposesplaceMark/getSymbolAt, never the raw grid. Nothing outsideBoardcan put a mark somewhere without going through the one method that also checks the cell is empty - the invariant "a filled cell never gets overwritten" lives in exactly one place.- What's missing for a real system: undo/redo, a spectator or replay feed, and an AI
opponent (which would slot in as another
Playerimplementation, since nothing aboutGameassumes a human is driving either side) are all out of scope for a 45-minute round but worth naming if asked "what would you add next."