Chapter 6: Bit Manipulation and Binary Representation
6.05Beginner6 topics20 min2 interactive animations

XOR techniques

XOR cancels equal values, which gives clean linear-time, constant-space solutions to a family of puzzles: the number that appears once, the missing number, and range XOR queries. This lesson also covers the one XOR trick you should avoid.

Learning goals

  • State the properties of XOR that make cancellation work
  • Find the unique element and the missing number in one pass
  • Handle variants: values repeated three times, and two unique values
  • Answer range XOR queries with prefix XOR, and explain why XOR swap is not worth using

Prerequisites

Bitwise operatorsArraysHash maps (for comparison)
6.05.1

XOR cancellation

XOR has four properties: a ^ a = 0, a ^ 0 = a, it is commutative (a ^ b = b ^ a) and associative. Together they mean that in a long chain of XORs, every value that appears twice cancels, whatever the order.

So x ^ y ^ x = y. The next lessons build on this: XOR everything and only the values with odd multiplicity survive.

PropertyExample
Self-inverse5 ^ 5 = 0
Identity5 ^ 0 = 5
Commutative3 ^ 6 = 6 ^ 3
Associative(1 ^ 2) ^ 3 = 1 ^ (2 ^ 3)
Undo(a ^ b) ^ b = a
6.05.2

Find unique number

In an array where every value appears twice except one, XOR all elements. The pairs cancel to 0 and the unique value remains. The animation shows the running XOR in binary: each repeated value flips its bits back.

Time is O(n) and space O(1). A hash map or sort would work too, but needs O(n) extra space or O(n log n) time.

Java
1static int findUnique(int[] a) {
2 int x = 0;
3 for (int v : a) x ^= v;
4 return x;
5}

Worst

O(n)

Space

O(1)

XOR: the number that appears once

Interactive · edit the input

Your input

Try

int[] arr · length 5

4
1
2
1
2
01234
Being XORed inAlready checkedMatch / answer

x = 0

05
04
03
02
01
00
Setup

x starts at 0, the identity for XOR: 0 ^ v = v.

Step 1 / 7
Java
1static int findUnique(int[] a) {
2 int x = 0;
3 for (int v : a) {
4 x ^= v; // equal pairs cancel: v ^ v == 0
5 }
6 return x;
7}

Live state

0 xors

x

0

No condition evaluated on this step
Execution trace (dry run table)Show 7 steps
StepLineStateWhat happens
1L2x=0x starts at 0, the identity for XOR: 0 ^ v = v.
2L4i=0 v=4 x=4x = 0 ^ 4 = 4.
3L4i=1 v=1 x=5x = 4 ^ 1 = 5.
4L4i=2 v=2 x=7x = 5 ^ 2 = 7.
5L4i=3 v=1 x=6x = 7 ^ 1 = 6. 1 appeared earlier, so this XOR cancels it out.
6L4i=4 v=2 x=4x = 6 ^ 2 = 4. 2 appeared earlier, so this XOR cancels it out.
7L6return=4return 4. Every value that appears twice cancelled itself (v ^ v = 0), and the order did not matter because XOR is commutative and associative. One pass, O(1) extra space.
6.05.3

Missing number

Given n distinct numbers taken from 0..n, one number is missing. XOR the indices 0..n-1 and the value n together with all the array values. Every number present appears twice (once as an index or n, once as a value) and cancels, leaving the missing one. Try the “Missing 0” and “Missing n” presets.

The sum formula n(n + 1)/2 - sum also works, but the sum can overflow int for large n. XOR cannot overflow.

Worst

O(n)

Space

O(1)

XOR: the missing number

Interactive · edit the input

Your input

Try

int[] arr · length 5

3
0
1
5
2
01234
Being XORed inAlready checkedMatch / answer

x = n = 5

04
03
12
01
10
Setup

The numbers 0..5 should all appear. XOR every expected number (the indices 0..4 and n) with every number present: everything cancels except the missing one.

Step 1 / 7
Java
1static int missing(int[] a) { // a holds 0..n except one value
2 int x = a.length; // start with n
3 for (int i = 0; i < a.length; i++) {
4 x ^= i ^ a[i]; // XOR in index and value
5 }
6 return x;
7}

Live state

0 xors

n

5

x

5

No condition evaluated on this step
Execution trace (dry run table)Show 7 steps
StepLineStateWhat happens
1L2n=5 x=5The numbers 0..5 should all appear. XOR every expected number (the indices 0..4 and n) with every number present: everything cancels except the missing one.
2L4i=0 a[i]=3 x=6x = 5 ^ 0 ^ 3 = 6.
3L4i=1 a[i]=0 x=7x = 6 ^ 1 ^ 0 = 7.
4L4i=2 a[i]=1 x=4x = 7 ^ 2 ^ 1 = 4.
5L4i=3 a[i]=5 x=2x = 4 ^ 3 ^ 5 = 2.
6L4i=4 a[i]=2 x=4x = 2 ^ 4 ^ 2 = 4.
7L6return=4return 4. It is the one number from 0..5 that has an index or n on one side but no matching value on the other. O(n) time, O(1) space, and no overflow risk, unlike the sum formula n(n+1)/2 - sum.
6.05.4

Single Number variants

If every value appears three times except one, plain XOR fails (three copies do not cancel). Instead count, for each bit position, how many numbers have that bit set, modulo 3. The bits that remain make up the unique number.

If two values appear once and the rest twice, XOR everything to get a ^ b. Any set bit of that result separates a from b. Use the lowest one to split the array into two groups, and XOR each group to recover a and b.

Java
1// every number appears 3 times except one
2static int singleOfThree(int[] a) {
3 int result = 0;
4 for (int bit = 0; bit < 32; bit++) {
5 int count = 0;
6 for (int v : a) count += (v >> bit) & 1;
7 if (count % 3 != 0) result |= 1 << bit;
8 }
9 return result;
10}
11
12// two numbers appear once, all others twice
13static int[] twoSingles(int[] a) {
14 int xor = 0;
15 for (int v : a) xor ^= v; // a ^ b
16 int lowest = xor & -xor; // a bit where a and b differ
17 int x = 0, y = 0;
18 for (int v : a) {
19 if ((v & lowest) != 0) x ^= v; else y ^= v;
20 }
21 return new int[] { x, y };
22}
6.05.5

XOR prefix

As with sums, define prefix[i] = a[0] ^ … ^ a[i-1]. Then the XOR of any range is prefix[r + 1] ^ prefix[l], because the shared prefix cancels. Range XOR queries take O(1) after an O(n) build.

It also solves counting problems: the number of subarrays with XOR equal to k is the number of pairs of prefixes whose XOR is k, found with a hash map of prefix values.

Java
1static int[] buildPrefixXor(int[] a) {
2 int[] p = new int[a.length + 1];
3 for (int i = 0; i < a.length; i++) p[i + 1] = p[i] ^ a[i];
4 return p;
5}
6static int rangeXor(int[] p, int l, int r) { return p[r + 1] ^ p[l]; } // inclusive l..r
7
8// count subarrays whose XOR is k
9static int countXorK(int[] a, int k) {
10 Map<Integer, Integer> seen = new HashMap<>();
11 seen.put(0, 1);
12 int prefix = 0, count = 0;
13 for (int v : a) {
14 prefix ^= v;
15 count += seen.getOrDefault(prefix ^ k, 0); // an earlier prefix p with p ^ prefix == k
16 seen.merge(prefix, 1, Integer::sum);
17 }
18 return count;
19}
6.05.6

XOR swap pitfalls

The XOR swap, a ^= b; b ^= a; a ^= b;, exchanges two variables without a temporary. It is a well-known trick but a poor choice: it is not faster than a temporary on modern CPUs and compilers, and it is harder to read.

It also has a real bug: if both operands refer to the same location, for example swapping arr[i] with arr[j] when i == j, the first step sets that value to x ^ x = 0, and the value is lost.

Java
1static void xorSwap(int[] arr, int i, int j) {
2 arr[i] ^= arr[j];
3 arr[j] ^= arr[i];
4 arr[i] ^= arr[j];
5}
6int[] a = {7, 8};
7xorSwap(a, 0, 1); // [8, 7] works
8xorSwap(a, 1, 1); // [8, 0] the element was zeroed, because i == j
9
10static void swap(int[] arr, int i, int j) { int t = arr[i]; arr[i] = arr[j]; arr[j] = t; } // prefer this

Practice ladder

Now try it yourself

Easy

  • Single number (every other appears twice)
  • Missing number in 0..n
  • Check whether two numbers have opposite signs using XOR

Medium

  • Single number III (two values appear once)
  • Single number II (others appear three times)
  • XOR queries of a subarray

Advanced

  • Count subarrays with XOR equal to k
  • Maximum XOR of two numbers in an array (bitwise trie)
  • Find the number that appears odd times when several numbers do

Key takeaways

  • XOR is self-inverse, commutative and associative, so equal pairs cancel in any order.
  • One XOR pass finds the unique or missing number in O(n) time and O(1) space with no overflow.
  • Prefix XOR answers range XOR queries; skip the XOR swap.