PracHub
QuestionsPremiumLearningGuidesInterview PrepNEWCoaches

Quick Overview

This question evaluates a candidate's understanding of efficient algorithm design and minimal state management for a Tic-Tac-Toe engine on an n x n board, including constraints such as O(1) time per move and O(n) space.

  • Medium
  • Databricks
  • Coding & Algorithms
  • Software Engineer

Design an efficient Tic-Tac-Toe engine

Company: Databricks

Role: Software Engineer

Category: Coding & Algorithms

Difficulty: Medium

Interview Round: Onsite

Design a Tic-Tac-Toe engine on an n x n board. Implement move(row, col, player) -> result where result indicates no winner, player1 wins, player2 wins, or invalid move. Achieve O( 1) time per move and O(n) space by maintaining minimal state. Extend the API to support reset() and an optional undo(). Write comprehensive tests for edge cases (repeated moves, out-of-bounds, early wins, and filled board with no winner).

Quick Answer: This question evaluates a candidate's understanding of efficient algorithm design and minimal state management for a Tic-Tac-Toe engine on an n x n board, including constraints such as O(1) time per move and O(n) space.

Design a Tic-Tac-Toe engine for an n x n board. Implement a function solution(n, operations) that processes engine commands in order and returns the result of each command. Supported commands: - ("move", row, col, player): place player 1 or player 2 at (row, col). - ("undo",): remove the last valid move. - ("reset",): clear the board and the move history. Return values: - "NONE": valid move, no winner yet. - "P1": player 1 wins on this move. - "P2": player 2 wins on this move. - "DRAW": the board becomes full and nobody wins. - "INVALID": invalid command or invalid move. - "UNDONE": undo succeeded. - "RESET": reset succeeded. A move is invalid if: - row or col is out of bounds, - the cell is already occupied, - player is not 1 or 2, - or the game has already ended in a win or draw. Turn order is not enforced; the player number is supplied in each move command. Your engine must detect wins efficiently without scanning entire rows, columns, or diagonals after every move. The classic win-tracking state should be O(n), while repeated-move detection and undo support may store occupied cells and move history.

Constraints

  • 1 <= n <= 2000
  • 1 <= len(operations) <= 2 * 10^5
  • Each operation is one of ("move", row, col, player), ("undo",), or ("reset",)
  • Winner detection after a move should be O(1) average time without rescanning the board

Examples

Input: (3, [("move", 0, 0, 1), ("move", 0, 0, 2), ("move", 3, 0, 1), ("undo",), ("undo",)])

Expected Output: ["NONE", "INVALID", "INVALID", "UNDONE", "INVALID"]

Explanation: The second move reuses an occupied cell, the third is out of bounds, the fourth undoes the only valid move, and the final undo fails because history is empty.

Input: (3, [("move", 0, 0, 1), ("move", 0, 1, 2), ("move", 1, 1, 1), ("move", 0, 2, 2), ("move", 2, 2, 1), ("move", 2, 0, 2)])

Expected Output: ["NONE", "NONE", "NONE", "NONE", "P1", "INVALID"]

Explanation: Player 1 completes the main diagonal on the fifth command. After a win, further moves are invalid until an undo or reset happens.

Input: (3, [("move", 0, 0, 1), ("move", 0, 1, 2), ("move", 0, 2, 1), ("move", 1, 1, 2), ("move", 1, 0, 1), ("move", 1, 2, 2), ("move", 2, 1, 1), ("move", 2, 0, 2), ("move", 2, 2, 1)])

Expected Output: ["NONE", "NONE", "NONE", "NONE", "NONE", "NONE", "NONE", "NONE", "DRAW"]

Explanation: All cells are filled without any row, column, or diagonal being completed by a single player.

Input: (1, [("move", 0, 0, 3), ("move", 0, 0, 2), ("undo",), ("move", 0, 0, 1)])

Expected Output: ["INVALID", "P2", "UNDONE", "P1"]

Explanation: Player 3 is invalid. On a 1x1 board, the first valid move wins immediately. Undo reopens the game, allowing the last move.

Input: (3, [("move", 0, 0, 1), ("move", 1, 1, 2), ("reset",), ("undo",), ("move", 2, 2, 1)])

Expected Output: ["NONE", "NONE", "RESET", "INVALID", "NONE"]

Explanation: Reset clears both the board and move history, so the following undo is invalid.

Input: (2, [("move", 0, 0, 1), ("undo", 1), ("foo",), ("reset",), ("move", 1, 1, 2)])

Expected Output: ["NONE", "INVALID", "INVALID", "RESET", "NONE"]

Explanation: Malformed and unknown commands are invalid and do not affect the board. Reset clears the earlier move.

Solution

def solution(n, operations):
    def is_valid_int(x):
        return isinstance(x, int) and not isinstance(x, bool)

    size = max(n, 0)
    rows = [0] * size
    cols = [0] * size
    row_ver = [0] * size
    col_ver = [0] * size

    diag = 0
    anti = 0
    diag_ver = 0
    anti_ver = 0
    version = 1

    board = {}
    history = []
    moves = 0
    game_over = False
    results = []

    for op in operations:
        if not isinstance(op, (list, tuple)) or len(op) == 0:
            results.append("INVALID")
            continue

        cmd = op[0]

        if cmd == "move":
            if len(op) != 4:
                results.append("INVALID")
                continue

            row, col, player = op[1], op[2], op[3]

            if not (is_valid_int(row) and is_valid_int(col) and is_valid_int(player)):
                results.append("INVALID")
                continue

            if player not in (1, 2):
                results.append("INVALID")
                continue

            if row < 0 or row >= n or col < 0 or col >= n:
                results.append("INVALID")
                continue

            if game_over or (row, col) in board:
                results.append("INVALID")
                continue

            delta = 1 if player == 1 else -1

            if row_ver[row] != version:
                rows[row] = 0
                row_ver[row] = version
            rows[row] += delta
            row_total = rows[row]

            if col_ver[col] != version:
                cols[col] = 0
                col_ver[col] = version
            cols[col] += delta
            col_total = cols[col]

            diag_total = None
            anti_total = None

            if row == col:
                if diag_ver != version:
                    diag = 0
                    diag_ver = version
                diag += delta
                diag_total = diag

            if row + col == n - 1:
                if anti_ver != version:
                    anti = 0
                    anti_ver = version
                anti += delta
                anti_total = anti

            board[(row, col)] = delta
            history.append((row, col, delta))
            moves += 1

            if row_total == n or col_total == n or diag_total == n or anti_total == n:
                game_over = True
                results.append("P1")
            elif row_total == -n or col_total == -n or diag_total == -n or anti_total == -n:
                game_over = True
                results.append("P2")
            elif moves == n * n:
                game_over = True
                results.append("DRAW")
            else:
                results.append("NONE")

        elif cmd == "undo":
            if len(op) != 1 or not history:
                results.append("INVALID")
                continue

            row, col, delta = history.pop()
            board.pop((row, col), None)

            if row_ver[row] != version:
                rows[row] = 0
                row_ver[row] = version
            rows[row] -= delta

            if col_ver[col] != version:
                cols[col] = 0
                col_ver[col] = version
            cols[col] -= delta

            if row == col:
                if diag_ver != version:
                    diag = 0
                    diag_ver = version
                diag -= delta

            if row + col == n - 1:
                if anti_ver != version:
                    anti = 0
                    anti_ver = version
                anti -= delta

            moves -= 1
            game_over = False
            results.append("UNDONE")

        elif cmd == "reset":
            if len(op) != 1:
                results.append("INVALID")
                continue

            version += 1
            board = {}
            history = []
            moves = 0
            game_over = False
            results.append("RESET")

        else:
            results.append("INVALID")

    return results

Time complexity: O(len(operations)) total on average, with O(1) average work per command. Space complexity: O(n + k), where k is the number of currently occupied cells (and equivalently the current valid move history size).

Hints

  1. Track each row and column with a running balance: +1 for player 1 and -1 for player 2. If any absolute value reaches n, that player has won.
  2. For undo, store each valid move on a stack so you can subtract its contribution from the row, column, and diagonal counters.
Last updated: May 17, 2026

Loading coding console...

PracHub

Master your tech interviews with 7,500+ real questions from top companies.

Product

  • Questions
  • Learning Tracks
  • Interview Guides
  • Resources
  • Premium
  • For Universities
  • Student Access

Browse

  • By Company
  • By Role
  • By Category
  • Topic Hubs
  • SQL Questions
  • Compare Platforms
  • Discord Community

Support

  • support@prachub.com
  • (916) 541-4762

Legal

  • Privacy Policy
  • Terms of Service
  • About Us

© 2026 PracHub. All rights reserved.

Related Coding Questions

  • Find the Best Commute Mode - Databricks (medium)
  • Partition a Target String by Source Substrings - Databricks (medium)
  • Design In-Memory QPS Counter - Databricks (medium)
  • Implement Random Connectivity and Grid Routing - Databricks
  • Implement a Snapshot Set Iterator - Databricks (hard)