Solve 3x3 Tic-Tac-Toe with Backtracking: Perfect-Play Result and Best Move

Quick Overview

Write a backtracking solver for 3x3 tic-tac-toe that determines the result of a given position under perfect play by both sides and returns a best move for the player to move. It tests win detection, recursive game-tree search, undoing moves correctly, and reasoning about search cost, memoization and pruning.

Solve 3x3 Tic-Tac-Toe with Backtracking: Perfect-Play Result and Best Move

Company: Decagon

Role: Software Engineer

Category: Software Engineering Fundamentals

Difficulty: medium

Interview Round: Technical Screen

Write a program that solves 3×3 tic-tac-toe with backtracking, that is, by trying a move, searching everything that can follow it, and undoing the move before trying the next one. The source reports only the game and that a standard backtracking solution was expected; the exact function contract is not reported. This practice version uses the common form of the task: given a position, determine the result of the game if both players play perfectly from that position on, and return a move for the player to move that achieves that result. ```python def solve(board: list[str]) -> tuple[str, tuple[int, int] | None]: ... ``` - `board` holds three strings of three characters each: `'X'`, `'O'`, or `'.'` for an empty cell. Row `0` is the top row and column `0` is the leftmost column. - X always moves first, so X is to move when both players have the same number of marks, and O is to move when X has one more. - A player wins by filling a row, a column or a diagonal with their mark. The game ends at a win or when the board is full. - Return `(result, move)`. `result` is `"X"` or `"O"` if that player wins with perfect play by both sides, and `"draw"` otherwise. `move` is the `(row, col)` of an empty cell where the player to move should play to achieve `result`, or `None` if the game is already over. Examples: ```text board = ["XX.", "OO.", "..."] result: ("X", (0, 2)) ``` X is to move and completes the top row. ```text board = ["...", "...", "..."] result: ("draw", any empty cell) ``` From the empty board neither player can force a win, so the result is a draw. ```hint Value of a position Define what a finished game is worth, then express what an unfinished position is worth in terms of the positions one move later, remembering whose turn it is. ``` ```hint One board, many tries Keep a single board for the whole search. Make sure every mark you place is removed again on every path out of that step. ``` ### Constraints and Clarifications - Assume the position is reachable in a real game unless you choose to validate it. - If several moves achieve the best result, any one of them is acceptable unless you state a tie rule. ### Clarifying Questions - Is the expected output the result of the position, a best move, both, or something else, such as the number of possible games? The source does not say. - Is a position given, or should the search always start from the empty board? - Should invalid positions be rejected, for example impossible mark counts or both players having three in a row? - When several moves achieve the same result, should the program prefer a faster win or a slower loss? ### What a Strong Answer Covers - Correct end-of-game detection: all eight lines, a full board, and a win checked before a full board - A recursive search that alternates players, with X seeking the best result for X and O the best result for O - Backtracking discipline: every placed mark is removed, so the board is unchanged when the search returns - Correct identification of the player to move, and correct output when the game is already over - The cost of the search, why exhaustive search is affordable on 3×3, and how memoization or pruning reduces it - Testing on an immediate win, a forced block, the empty board and finished boards ### Follow-up Questions - How many positions does the search visit from the empty board, and how would memoization or alpha-beta pruning change that? - How would you make the program prefer the fastest win and the slowest loss? - How would you generalize the solver to an n×n board where k in a row wins, and where does exhaustive search stop being practical? - Using the same backtracking, how would you count how many different complete games of tic-tac-toe can be played?

Overview: Write a backtracking solver for 3x3 tic-tac-toe that determines the result of a given position under perfect play by both sides and returns a best move for the player to move. It tests win detection, recursive game-tree search, undoing moves correctly, and reasoning about search cost, memoization and pruning.

Decagon logo
Decagon
Apr 12, 2026
mediumSoftware EngineerTechnical ScreenSoftware Engineering Fundamentals
2
0

Write a program that solves 3×3 tic-tac-toe with backtracking, that is, by trying a move, searching everything that can follow it, and undoing the move before trying the next one.

The source reports only the game and that a standard backtracking solution was expected; the exact function contract is not reported. This practice version uses the common form of the task: given a position, determine the result of the game if both players play perfectly from that position on, and return a move for the player to move that achieves that result.

def solve(board: list[str]) -> tuple[str, tuple[int, int] | None]:
    ...
  • board holds three strings of three characters each: 'X' , 'O' , or '.' for an empty cell. Row 0 is the top row and column 0 is the leftmost column.
  • X always moves first, so X is to move when both players have the same number of marks, and O is to move when X has one more.
  • A player wins by filling a row, a column or a diagonal with their mark. The game ends at a win or when the board is full.
  • Return (result, move) . result is "X" or "O" if that player wins with perfect play by both sides, and "draw" otherwise. move is the (row, col) of an empty cell where the player to move should play to achieve result , or None if the game is already over.

Examples:

board = ["XX.",
         "OO.",
         "..."]
result: ("X", (0, 2))

X is to move and completes the top row.

board = ["...",
         "...",
         "..."]
result: ("draw", any empty cell)

From the empty board neither player can force a win, so the result is a draw.

Constraints and Clarifications

  • Assume the position is reachable in a real game unless you choose to validate it.
  • If several moves achieve the best result, any one of them is acceptable unless you state a tie rule.

Clarifying Questions Guidance

  • Is the expected output the result of the position, a best move, both, or something else, such as the number of possible games? The source does not say.
  • Is a position given, or should the search always start from the empty board?
  • Should invalid positions be rejected, for example impossible mark counts or both players having three in a row?
  • When several moves achieve the same result, should the program prefer a faster win or a slower loss?

What a Strong Answer Covers Guidance

  • Correct end-of-game detection: all eight lines, a full board, and a win checked before a full board
  • A recursive search that alternates players, with X seeking the best result for X and O the best result for O
  • Backtracking discipline: every placed mark is removed, so the board is unchanged when the search returns
  • Correct identification of the player to move, and correct output when the game is already over
  • The cost of the search, why exhaustive search is affordable on 3×3, and how memoization or pruning reduces it
  • Testing on an immediate win, a forced block, the empty board and finished boards

Follow-up Questions Guidance

  • How many positions does the search visit from the empty board, and how would memoization or alpha-beta pruning change that?
  • How would you make the program prefer the fastest win and the slowest loss?
  • How would you generalize the solver to an n×n board where k in a row wins, and where does exhaustive search stop being practical?
  • Using the same backtracking, how would you count how many different complete games of tic-tac-toe can be played?