Skip to content
DSA Patterns

Learn

Bitwise XOR

XOR's four properties and the problems they unlock: finding the single number in O(1) space, splitting two singles by a set bit, and the bit-counting idioms worth memorising.

4 min readUpdated 2 Sept 2026

#Bit Manipulation#Math#O(1) space

XOR problems are pattern-matching on four algebraic facts. There is no algorithm to learn — either you know the identities and the solution is a line, or you do not and the problem looks impossible. That makes this the highest-leverage section on the sheet per minute spent.

The four properties

x ^ x = 0          a value cancels itself
x ^ 0 = x          zero is the identity
a ^ b = b ^ a      commutative
(a ^ b) ^ c = a ^ (b ^ c)    associative

Commutativity and associativity together mean order does not matter, so XOR-ing a whole array collapses every paired value to zero and leaves only what is unpaired.

def single_number(nums):
    result = 0
    for x in nums:
        result ^= x
    return result

O(n) time, O(1) space, works on unsorted input, and no hash map. The same reasoning solves

Missing Number: XOR every index together with every value, and everything pairs off

except the index that has no value.

def missing_number(nums):
    result = len(nums)
    for i, x in enumerate(nums):
        result ^= i ^ x
    return result

Two singles: split by a set bit

Single Number III — every value appears twice except two. XOR-ing everything gives

a ^ b, which is not either answer. But any bit set in a ^ b is a bit where a and b differ, so partitioning the array on that bit puts a in one group and b in the other, each of which is now a plain Single Number.

def single_number_iii(nums):
    xor_all = 0
    for x in nums:
        xor_all ^= x

    lowest_bit = xor_all & -xor_all      # isolates the rightmost set bit

    a = b = 0
    for x in nums:
        if x & lowest_bit:
            a ^= x
        else:
            b ^= x
    return [a, b]

x & -x is the idiom to remember: two's complement makes -x the inverse of x plus one, so the AND leaves exactly the lowest set bit standing. It also powers Fenwick trees, which is where you meet it again.

Three of a kind: count bits mod 3

Single Number II — every value appears three times except one. XOR cannot help, because

x ^ x ^ x = x. Count set bits per position instead and take the count modulo 3; whatever remains is the answer's bit pattern.

def single_number_ii(nums):
    result = 0
    for bit in range(32):
        count = sum((x >> bit) & 1 for x in nums)
        if count % 3:
            result |= 1 << bit
    if result >= 2 ** 31:                # Python has unbounded ints
        result -= 2 ** 32                # reinterpret as a signed 32-bit value
    return result

The generalisation: values appearing k times cancel under "count mod k", and XOR is just the k = 2 case done in parallel across all bits at once.

The idioms worth memorising

IdiomMeaning
x & (x - 1)clears the lowest set bit — loop it to count bits in O(set bits)
x & -xisolates the lowest set bit
x & 1is odd
x >> 1divide by two
x ^ (1 << i)flip bit i
x & (1 << i)test bit i

Number of 1 Bits is x & (x - 1) in a loop: each iteration removes one set bit, so it

runs once per set bit rather than 32 times.

def hamming_weight(n):
    count = 0
    while n:
        n &= n - 1
        count += 1
    return count

Maximum XOR of Two Numbers

The one that is not an identity trick. Insert every number into a binary trie, most significant bit first, then for each number walk the trie preferring the opposite bit at every level — the opposite bit is what makes the XOR's high bits 1, and a high bit is worth more than every lower bit combined. O(n × 32) instead of O(n²).

Complexity

O(n) time and O(1) space for the XOR-based ones — that combination is precisely why these get asked when a hash map would be the obvious answer. The bit-counting variants are O(32n), and the trie is O(32n) time with O(32n) space.

Mistakes that cost the round

  • Trying to XOR the k = 3 problem. x ^ x ^ x = x — odd multiplicities do not cancel.
  • Signed-integer handling. In Python, integers are unbounded, so the bit-count solution

needs the explicit two's-complement fixup above. In Java, use >>> rather than >>.

  • Assuming XOR gives you the values. a ^ b is neither a nor b; you need the

splitting bit to recover them.

  • Confusing `x & -x` with `x & (x - 1)`. One isolates the lowest set bit, the other

clears it.

What to drill

  1. Single Number — the cancellation identity.
  2. Number of 1 Bitsx & (x - 1).
  3. Single Number III — split by the differing bit.
  4. Single Number II — count bits mod 3.
  5. Maximum XOR of Two Numbers in an Array — the binary trie.

All on the 22 DSA Patterns sheet.

Frequently asked

Why does XOR find the number that appears once?

Because x ^ x = 0, x ^ 0 = x, and XOR is commutative and associative. Order does not matter, so XOR-ing the whole array lets every pair cancel to zero, leaving only the unpaired value. It runs in O(n) time and O(1) space with no hash map.

How do you find two numbers that each appear once?

XOR everything to get a ^ b. Any set bit in that result is a bit where the two answers differ, so isolate one with x & -x and partition the array on it. Each partition now contains exactly one unpaired number, so XOR each group separately.

What does `x & (x - 1)` do?

It clears the lowest set bit of x. Looping it until x is zero counts the set bits in as many iterations as there are 1s, rather than always looping 32 times. The related idiom x & -x does the opposite — it isolates that lowest set bit rather than clearing it.

Related