Skip to content
← All posts

XOR Queries of a Subarray: Prefix XOR Pattern

4 min read
leetcodeproblemmediumarraysbit-manipulation

LeetCode XOR Queries of a Subarray (Problem 1310) gives you an array arr and a list of queries. Each query is a pair [left, right], and you need to return the XOR of all elements from arr[left] to arr[right] inclusive.

A brute force approach would loop through the subarray for each query. But with up to 30,000 queries on an array of up to 30,000 elements, that gets slow fast. The trick is to precompute a prefix XOR array so every query takes O(1).

arr1[0]3[1]4[2]8[3]query [0, 1]prefix0[0]1[1]2[2]6[3]14[4]answerprefix[2] XOR prefix[0]= 2 XOR 0 = 2
The prefix XOR array lets you answer any range XOR query in O(1). For query [0, 1], XOR prefix[2] with prefix[0] to get 2.

Why this problem matters

This is the XOR version of prefix sums. If you have seen prefix sums before, you already know the template. The only difference is that you swap addition for XOR and subtraction for... also XOR. That is the beautiful thing about XOR: the same operation that builds the prefix also undoes it. The self-cancellation property (a XOR a = 0) plays the exact role that subtraction plays for prefix sums.

Once you internalize prefix XOR, you can apply the same idea to any associative, invertible operation. It shows up in competitive programming, interview problems, and systems code dealing with checksums and error detection.

The key insight

Build a prefix XOR array where prefix[i] = arr[0] XOR arr[1] XOR ... XOR arr[i-1], with prefix[0] = 0. Then the XOR of any subarray [left, right] is simply prefix[right + 1] XOR prefix[left].

Why does this work? When you XOR prefix[right + 1] with prefix[left], all the terms from arr[0] through arr[left - 1] appear in both prefix values. XOR is self-inverse, so those shared terms cancel to zero. What remains is exactly arr[left] XOR arr[left + 1] XOR ... XOR arr[right].

The solution

def xor_queries(arr: list[int], queries: list[list[int]]) -> list[int]:
    prefix = [0] * (len(arr) + 1)
    for i in range(len(arr)):
        prefix[i + 1] = prefix[i] ^ arr[i]

    result = []
    for left, right in queries:
        result.append(prefix[right + 1] ^ prefix[left])
    return result

The function builds the prefix array in one pass, then answers each query with a single XOR operation. No nested loops, no repeated work.

XOR is its own inverse: a ^ a = 0 and a ^ 0 = a. This means you never need a separate "undo" operation. The same ^ that builds the prefix also extracts the range. If you remember one thing from this problem, let it be this cancellation property.

Visual walkthrough

Step 1. Initialize prefix[0] = 0

prefix0[0]

Every prefix XOR array starts with 0. XOR-ing with 0 is the identity operation.

Step 2. Build prefix XOR: prefix[i+1] = prefix[i] XOR arr[i]

arr1[0]3[1]4[2]8[3]prefix0[0]1[1]2[2]6[3]14[4]

Walk through arr left to right, accumulating XOR values into the prefix array.

Step 3. Answer query [0, 1]: prefix[2] XOR prefix[0]

prefix0[0]1[1]2[2]6[3]14[4]= 2

prefix[right+1] XOR prefix[left] = prefix[2] XOR prefix[0] = 2 XOR 0 = 2. This equals arr[0] XOR arr[1] = 1 XOR 3 = 2.

Step 4. Answer query [1, 2]: prefix[3] XOR prefix[1]

prefix0[0]1[1]2[2]6[3]14[4]= 7

prefix[3] XOR prefix[1] = 6 XOR 1 = 7. This equals arr[1] XOR arr[2] = 3 XOR 4 = 7.

Step 5. Why cancellation works

prefix[3] = arr[0] XOR arr[1] XOR arr[2]prefix[1] = arr[0]result = arr[1] XOR arr[2] (arr[0] cancels)

XOR is self-inverse: a XOR a = 0. When you XOR prefix[right+1] with prefix[left], all terms before index left cancel out, leaving just the XOR of arr[left..right].

Notice that building the prefix array is the only real work. Once you have it, every query is a constant-time lookup and XOR. The prefix array is the precomputation that turns repeated O(n) work into O(1) answers.

Complexity analysis

ApproachTimeSpace
Prefix XORO(n + q)O(n)

Here n is the length of arr and q is the number of queries. Building the prefix array takes O(n). Each query takes O(1), and there are q of them. The prefix array uses O(n) extra space.

A brute force approach that iterates through the subarray for every query would be O(n * q) in the worst case. The prefix XOR approach eliminates that redundant work entirely.

The building blocks

1. Prefix XOR construction

prefix = [0] * (len(arr) + 1)
for i in range(len(arr)):
    prefix[i + 1] = prefix[i] ^ arr[i]

This is the same loop structure as prefix sums, just with ^ instead of +. The extra element at index 0 (set to 0) simplifies the range query formula. Without it, you would need a special case when left == 0.

2. Range XOR query

def range_xor(prefix, left, right):
    return prefix[right + 1] ^ prefix[left]

One XOR, two lookups. The prefix values at right + 1 and left encode everything you need. The terms before left cancel out, and the terms from left through right remain.

Edge cases

  • Single element query. When left == right, you get prefix[left + 1] ^ prefix[left], which simplifies to arr[left]. The formula handles it naturally.
  • Full array query. When left = 0 and right = len(arr) - 1, you get prefix[len(arr)] ^ prefix[0], which is the XOR of the entire array. The prefix[0] = 0 initialization makes this clean.
  • All elements are zero. Every prefix value is 0, and every query returns 0. No special handling needed.
  • All elements are the same. If every element is x, then XOR-ing an even number of them gives 0 and an odd number gives x. The prefix array captures this pattern correctly.

From understanding to recall

You have seen the prefix XOR pattern and traced through the examples. But the next time a problem asks you to compute XOR over subarrays, will the solution come to you immediately? That is the gap between understanding and recall.

The fix is spaced repetition. You practice building the prefix array and writing the range query formula from scratch, at increasing intervals, until the pattern is automatic. After a few reps, you stop thinking about XOR tables and cancellation proofs. You just see "range XOR" and your hands write the prefix loop.

Related posts

Prefix XOR is one of those patterns that pays dividends far beyond a single problem. Master it once, and you will recognize it everywhere: checksums, parity checks, range queries, and more.