title: Chalkboard XOR Game
slug: chalkboard-xor-game
difficulty: hard
leetcode_id: 810
leetcode_url: https://leetcode.com/problems/chalkboard-xor-game/
categories:
  - arrays
  - math
patterns:
  - greedy

function_signature: "def xor_game(nums: list[int]) -> bool:"

test_cases:
  visible:
    - input: { nums: [1, 1, 2] }
      expected: false
    - input: { nums: [0, 1] }
      expected: true
    - input: { nums: [1, 2, 3] }
      expected: true
  hidden:
    - input: { nums: [1] }
      expected: false
    - input: { nums: [0, 0] }
      expected: true
    - input: { nums: [1, 1] }
      expected: true
    - input: { nums: [1, 2, 3, 4] }
      expected: true
    - input: { nums: [5, 5, 5] }
      expected: false
    - input: { nums: [1, 2, 4, 8] }
      expected: true

description: |
  You are given an array of integers `nums` representing the numbers written on a chalkboard.

  Alice and Bob take turns erasing exactly one number from the chalkboard, with Alice starting first. If erasing a number causes the bitwise XOR of all the elements of the chalkboard to become `0`, then that player loses. The bitwise XOR of one element is that element itself, and the bitwise XOR of no elements is `0`.

  Also, if any player starts their turn with the bitwise XOR of all the elements of the chalkboard equal to `0`, then that player wins.

  Return `true` *if and only if Alice wins the game, assuming both players play optimally*.

constraints: |
  - `1 <= nums.length <= 1000`
  - `0 <= nums[i] < 2^16`

examples:
  - input: "nums = [1,1,2]"
    output: "false"
    explanation: "Alice has two choices: erase 1 or erase 2. If she erases 1, nums becomes [1, 2] with XOR = 3. Bob can then force Alice to erase the last element and lose. If Alice erases 2, nums becomes [1, 1] with XOR = 0, so Alice loses immediately."
  - input: "nums = [0,1]"
    output: "true"
    explanation: "Alice can erase 1, leaving [0]. The XOR is 0, but it was already non-zero before her move completed, and now Bob faces XOR = 0 at the start of his turn, so Bob loses."
  - input: "nums = [1,2,3]"
    output: "true"
    explanation: "XOR of [1,2,3] = 0. Alice starts with XOR = 0, so she wins immediately."

explanation:
  intuition: |
    This problem looks like it requires game simulation or minimax, but there's a beautiful mathematical insight that reduces it to a simple formula.

    Think about XOR properties: if the XOR of all elements is already `0` when it's your turn, you win. The key insight is understanding *when* Alice can be forced into a losing position.

    Alice loses only if **every** number she could erase would make the XOR become `0`. Let's think about when this happens. If the current XOR is `X ≠ 0`, and Alice must pick a number `nums[i]` such that `X XOR nums[i] = 0`, then `nums[i] = X`. This means Alice loses only if **all** remaining numbers equal the current XOR value — which means all numbers are identical.

    But here's the clever part: if all `n` numbers are identical (say, all equal to `v`), then:
    - If `n` is even: XOR = `v XOR v XOR ... = 0`, so Alice wins immediately
    - If `n` is odd: XOR = `v`, and Alice must erase `v`, leaving an even count where Bob faces XOR = 0 and wins

    Generalising this: Alice loses only when forced to make XOR = 0. With an even number of elements and non-zero XOR, Alice can always find a "safe" move (at least one element whose removal doesn't make XOR = 0). This is because if removing **any** element made XOR = 0, all elements would need to equal the current XOR, making them all identical — but then with even count, XOR would already be 0.

    Therefore: **Alice wins if and only if the initial XOR is 0, OR the array has an even length.**

  approach: |
    The solution is remarkably simple once you understand the game theory:

    **Step 1: Calculate the XOR of all elements**

    - Compute `xor_sum` by XORing all elements in `nums`
    - This tells us the starting game state

    &nbsp;

    **Step 2: Check the two winning conditions for Alice**

    - **Condition 1:** If `xor_sum == 0`, Alice wins immediately (she starts with XOR = 0)
    - **Condition 2:** If `len(nums)` is even, Alice wins (she can always find a safe move)

    &nbsp;

    **Step 3: Return the result**

    - Return `True` if either condition is met, `False` otherwise
    - This covers all cases: Alice wins if she starts with XOR = 0, or if she has an even number of elements to work with

    &nbsp;

    The mathematical proof: with even length and non-zero XOR, if removing any element made XOR = 0, then each element `nums[i]` would equal the total XOR. But `n` identical values XORed together (with `n` even) gives 0, contradicting our assumption that XOR ≠ 0.

  common_pitfalls:
    - title: Attempting Game Simulation
      description: |
        A natural first instinct is to simulate the game using recursion or dynamic programming, trying all possible moves for both players.

        With up to 1000 elements, this approach has exponential complexity — the game tree branches at each turn, leading to `O(n!)` possibilities. This will cause **Time Limit Exceeded**.

        The key is recognising this as a **mathematical problem** with a closed-form solution, not a search problem.
      wrong_approach: "Recursive simulation with memoisation"
      correct_approach: "Use XOR properties and parity analysis"

    - title: Forgetting the Even-Length Guarantee
      description: |
        Some solutions correctly check if XOR = 0 but miss the even-length winning condition.

        The insight is subtle: with an even number of elements and non-zero XOR, Alice can *always* find at least one element whose removal doesn't make XOR = 0. This is guaranteed by the pigeonhole principle applied to XOR values.
      wrong_approach: "Only checking if initial XOR is 0"
      correct_approach: "Check both XOR = 0 and even length conditions"

    - title: Misunderstanding the Winning Condition
      description: |
        The rules have a subtle distinction:
        - If you **start** your turn with XOR = 0, you **win**
        - If your move **causes** XOR to become 0, you **lose**

        These are different! Starting with XOR = 0 is an immediate win, not a loss. This catches players who think XOR = 0 is always bad.
      wrong_approach: "Thinking XOR = 0 means current player loses"
      correct_approach: "XOR = 0 at turn start means win; causing XOR = 0 means loss"

  key_takeaways:
    - "**Game theory simplification**: Many two-player games have elegant mathematical solutions — look for invariants and parity arguments before attempting simulation"
    - "**XOR properties**: XOR of identical values cancels out (even count = 0), which drives the parity-based solution"
    - "**Constraint analysis**: The even-length guarantee comes from a proof by contradiction using XOR algebra"
    - "**Pattern recognition**: This problem teaches that 'hard' brainteasers often have O(n) or O(1) solutions hiding behind the complexity"

  time_complexity: "O(n). We traverse the array once to compute the XOR of all elements."
  space_complexity: "O(1). We only use a single variable (`xor_sum`) regardless of input size."

solutions:
  - approach_name: XOR and Parity Check
    is_optimal: true
    code: |
      def xor_game(nums: list[int]) -> bool:
          # Calculate XOR of all elements
          xor_sum = 0
          for num in nums:
              xor_sum ^= num

          # Alice wins if:
          # 1. XOR is already 0 (she wins immediately), OR
          # 2. Array has even length (she can always find a safe move)
          return xor_sum == 0 or len(nums) % 2 == 0
    explanation: |
      **Time Complexity:** O(n) — Single pass to compute XOR.

      **Space Complexity:** O(1) — Only one integer variable used.

      The solution uses game theory insight: Alice wins if she starts with XOR = 0, or if she has an even number of elements (guaranteeing a safe move exists). This elegant formula avoids expensive game tree simulation.

  - approach_name: One-Liner with Reduce
    is_optimal: true
    code: |
      from functools import reduce
      from operator import xor

      def xor_game(nums: list[int]) -> bool:
          # XOR all elements; Alice wins if result is 0 or length is even
          return reduce(xor, nums) == 0 or len(nums) % 2 == 0
    explanation: |
      **Time Complexity:** O(n) — Reduce iterates through all elements.

      **Space Complexity:** O(1) — No additional space beyond the accumulator.

      A more Pythonic version using `functools.reduce` with the XOR operator. Functionally identical to the explicit loop but more concise.
