0Pricing
DSA Interview Prep · Lesson

Single Number and XOR Properties

Use XOR's self-inverse property to find the one element appearing once in a list where all others appear twice, then extend to single-number-II and III.

Single Number and XOR Properties is a free DSA Interview Prep lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the DSA Interview Prep learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

The Single Number Problem

The Single Number problem (LeetCode 136) asks: given an array where every element appears exactly twice except one, find the element that appears only once. The O(n) time and O(1) space constraint rules out hash maps (O(n) space) and sorting (O(n log n) time or O(n) space for the sort).

The elegant solution uses XOR. XOR every element together. Since identical elements cancel (a ^ a = 0) and XOR is commutative and associative, all paired elements vanish, leaving only the single element. This is one of the most satisfying O(n)/O(1) solutions in all of competitive programming.

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

# All pairs cancel, leaving the lone element
print(single_number([2, 2, 1]))              # 1
print(single_number([4, 1, 2, 1, 2]))        # 4
print(single_number([1]))                    # 1
print(single_number([7, 3, 5, 3, 7]))        # 5

# Even more concise with functools.reduce
from functools import reduce
from operator import xor
print(reduce(xor, [2, 2, 1]))  # 1

Why XOR Works: Three Key Properties

XOR's power comes from three algebraic properties working together:

  • Self-inverse: a ^ a = 0 — identical values cancel each other out
  • Identity: a ^ 0 = a — XOR with zero leaves values unchanged
  • Commutativity and Associativity: order does not matter, groupings do not matter

These three properties together mean that XOR over a multiset collapses all elements appearing an even number of times to 0, leaving only elements appearing an odd number of times. For Single Number I, exactly one element appears once (odd), so it is the XOR result.

# Demonstrating the three XOR properties
print('Self-inverse: a ^ a = 0')
for a in [5, 13, 255, 0]:
    print(f'  {a} ^ {a} = {a ^ a}')

print('Identity: a ^ 0 = a')
for a in [5, 13, 0, 1024]:
    print(f'  {a} ^ 0 = {a ^ 0}')

print('Commutativity and Associativity:')
a, b, c = 3, 5, 7
print(f'  a^b^c = {a^b^c}')
print(f'  c^a^b = {c^a^b}')  # same result
print(f'  (a^b)^c = {(a^b)^c}')
print(f'  a^(b^c) = {a^(b^c)}')  # same result

Trace Through Single Number

Let us trace [4, 1, 2, 1, 2] step by step to see cancellation in action. We XOR all elements: 4 ^ 1 ^ 2 ^ 1 ^ 2. Because XOR is commutative, reorder as (1 ^ 1) ^ (2 ^ 2) ^ 4 = 0 ^ 0 ^ 4 = 4. The pairs cancel and only 4 remains.

In the actual algorithm, we do not reorder — we XOR left to right. But the final result is the same because commutativity and associativity guarantee that order does not affect the outcome. You can mentally group the pairs anywhere and they all cancel.

nums = [4, 1, 2, 1, 2]
result = 0
print(f'Start: result = {result} ({bin(result)})')
for n in nums:
    prev = result
    result ^= n
    print(f'XOR {n:2d}: {bin(prev):8s} ^ {bin(n):6s} = {bin(result):8s} = {result}')
print(f'Final: {result}')  # 4

# Alternative: show pair cancellation
print('\nMath view:')
print('4 ^ 1 ^ 2 ^ 1 ^ 2')
print('= 4 ^ (1^1) ^ (2^2)')
print('= 4 ^  0   ^  0')
print('= 4')

Single Number II: Every Element Appears Three Times

Single Number II (LeetCode 137): every element appears three times except one that appears once. XOR alone does not work — pairs no longer cancel in threes. Instead, we count how many times each bit appears across all numbers. If a bit appears in the target element, it contributes 1; in triple elements, it contributes 3. Take count mod 3 for each bit to isolate the target element's bits.

We can simulate this with two integer variables ones and twos acting as a bit-level counter modulo 3. This is a digital-logic approach: ones holds bits seen an odd number of times modulo 2, and twos holds bits seen twice modulo 3.

def single_number_II(nums):
    ones, twos = 0, 0
    for n in nums:
        ones = (ones ^ n) & ~twos   # bits seen 1 mod 3 times
        twos = (twos ^ n) & ~ones   # bits seen 2 mod 3 times
    return ones  # bits seen exactly once

print(single_number_II([2, 2, 3, 2]))    # 3
print(single_number_II([0, 1, 0, 1, 0, 1, 99]))  # 99

# Simpler but O(32) bit-by-bit approach
def single_number_II_simple(nums):
    result = 0
    for bit in range(32):
        total = sum((n >> bit) & 1 for n in nums)
        if total % 3 == 1:
            result |= (1 << bit)
    return result

print(single_number_II_simple([2, 2, 3, 2]))  # 3

Single Number III: Two Elements Appear Once

Single Number III (LeetCode 260): two elements each appear once; all others appear twice. XOR all elements to get a ^ b (the XOR of the two unique elements). Since a ≠ b, at least one bit in a ^ b is 1 — find the lowest set bit of a ^ b using diff = xor_all & (-xor_all).

This bit is 1 in exactly one of a or b. Partition all numbers into two groups based on whether that bit is set. XOR each group separately — paired elements cancel, leaving a from one group and b from the other.

def single_number_III(nums):
    xor_all = 0
    for n in nums:
        xor_all ^= n              # xor_all = a ^ b

    diff = xor_all & (-xor_all)  # isolate lowest differing bit

    a = 0
    for n in nums:
        if n & diff:              # group 1: has the diff bit set
            a ^= n
    b = xor_all ^ a              # a ^ b ^ a = b
    return [a, b]

print(sorted(single_number_III([1, 2, 1, 3, 2, 5])))   # [3, 5]
print(sorted(single_number_III([-1, 0])))               # [-1, 0]
print(sorted(single_number_III([0, 1])))                # [0, 1]

Finding the Missing Number with XOR

The Missing Number problem (LeetCode 268): given an array of n distinct numbers from 0 to n, find the missing one. XOR all numbers in the array with all numbers from 0 to n. Pairs cancel, leaving the missing number. This gives O(n) time and O(1) space.

Alternatively, use the arithmetic sum formula: expected = n*(n+1)//2, then subtract the actual sum. Both approaches are O(n)/O(1). XOR is more robust because it avoids potential integer overflow in languages with fixed-width integers.

def missing_number_xor(nums):
    n = len(nums)
    result = n              # start with n (the last expected value)
    for i, num in enumerate(nums):
        result ^= i ^ num   # XOR with both index and value
    return result

def missing_number_sum(nums):
    n = len(nums)
    expected = n * (n + 1) // 2
    return expected - sum(nums)

for nums, expected in [([3,0,1], 2), ([0,1], 2), ([9,6,4,2,3,5,7,0,1], 8)]:
    xor_ans = missing_number_xor(nums)
    sum_ans = missing_number_sum(nums)
    print(f'nums={nums}: XOR={xor_ans}, Sum={sum_ans}, expected={expected}')

XOR for Swap Without Temp Variable

XOR enables swapping two variables without a temporary variable. The trick is that a ^ b ^ a = b and a ^ b ^ b = a. Apply three XOR assignments in sequence: a ^= b, then b ^= a, then a ^= b. After all three, a holds the original b and b holds the original a.

Important caveat: this trick fails if a and b reference the same memory location (i.e., if they are the same variable). In that case, a ^= a sets a to 0 and the value is lost. In Python, tuple unpacking (a, b = b, a) is safer and clearer. XOR swap is mainly useful in C/embedded contexts with no extra memory.

# XOR swap
a, b = 17, 42
print(f'Before: a={a}, b={b}')
a ^= b   # a = 17 ^ 42
b ^= a   # b = 42 ^ (17 ^ 42) = 17
a ^= b   # a = (17 ^ 42) ^ 17 = 42
print(f'After:  a={a}, b={b}')   # a=42, b=17

# The caveat: same variable/reference => broken
c = 99
# If a and b pointed to same value:
c ^= c   # c = 0  (destroyed!)
print(f'Same-variable XOR swap: c={c}')  # 0, not 99

# Pythonic swap: always prefer this
a, b = 17, 42
a, b = b, a   # safe, clear, handles aliases
print(f'Pythonic: a={a}, b={b}')

XOR in Hashing and Checksums

XOR is a common building block in checksums and parity checks. XOR-ing all bytes in a data block produces a single-byte checksum. If a single bit flips during transmission, the checksum changes, detecting the error. This is simpler than CRC but catches all single-bit errors.

XOR is also used in RAID-5 parity: for three drives, store the XOR of two drives' data on the third. If one drive fails, XOR the remaining two to reconstruct the lost data. This is exactly the Single Number logic in reverse — the parity drive is the 'unique element' that encodes what cancels when all three are XOR'd.

# Simple XOR checksum
def xor_checksum(data):
    result = 0
    for byte in data:
        result ^= byte
    return result

data = [0x48, 0x65, 0x6C, 0x6C, 0x6F]  # 'Hello' in ASCII
checksum = xor_checksum(data)
print(f'Checksum: {hex(checksum)}')

# Detect corruption
corrupted = data[:]
corrupted[2] ^= 0xFF   # flip all bits of 3rd byte
new_checksum = xor_checksum(corrupted)
print(f'Original checksum: {hex(checksum)}')
print(f'Corrupted checksum: {hex(new_checksum)}')
print(f'Error detected: {checksum != new_checksum}')

# RAID-5 parity recovery
d1 = [1, 0, 1, 1]
d2 = [0, 1, 1, 0]
parity = [d1[i] ^ d2[i] for i in range(4)]
recovered = [parity[i] ^ d2[i] for i in range(4)]  # recover d1
print(f'd1={d1}, parity={parity}, recovered={recovered}')

XOR and Subset Problems

XOR appears in subset problems when you need to compute the XOR of all subsets. A key insight: for n elements, each element appears in exactly 2^(n-1) subsets. If n > 1, every element appears in an even number of subsets, so its XOR contribution cancels. The XOR of all subset-XORs is 0 for n > 1.

For n == 1, the only non-empty subset is the element itself, so the XOR-of-all-subsets is that element. This kind of reasoning — using properties of XOR and counting — is tested in advanced bit manipulation problems.

from itertools import combinations
from functools import reduce
from operator import xor

def xor_of_all_subsets(arr):
    n = len(arr)
    total_xor = 0
    for r in range(1, n + 1):
        for subset in combinations(arr, r):
            subset_xor = reduce(xor, subset)
            total_xor ^= subset_xor
    return total_xor

# For n > 1, each element appears 2^(n-1) times (even) => cancels
# Result is always 0 for n > 1
for arr in [[1,2,3], [5,7], [1], [1,2,3,4]]:
    result = xor_of_all_subsets(arr)
    predicted = arr[0] if len(arr) == 1 else 0
    print(f'arr={arr}: XOR of all subsets = {result}, predicted = {predicted}')

Interview Pattern: XOR for Uniqueness

Recognise the XOR-for-uniqueness pattern when a problem says: 'every element appears k times except one that appears m times where m mod k != 0'. For k=2, m=1 (Single Number I): XOR all elements. For k=3, m=1 (Single Number II): count bits mod 3. For k=2, m=1 with two uniques (Single Number III): XOR then split by lowest differing bit.

The general approach for arbitrary k is to count each bit's total occurrences and take mod k. If the count is non-zero, that bit belongs to the unique element. This gives an O(32n) = O(n) algorithm with O(1) space for any k.

def single_number_k_times(nums, k):
    '''Find the element that appears m times when all others appear k times.'''
    # Count each bit's occurrence and take mod k
    result = 0
    for bit in range(32):
        total = sum((n >> bit) & 1 for n in nums)
        if total % k != 0:
            result |= (1 << bit)
    # Handle negative 32-bit numbers
    if result >= (1 << 31):
        result -= (1 << 32)
    return result

# k=2, element appears once
print(single_number_k_times([2,2,1], 2))         # 1
# k=3, element appears once
print(single_number_k_times([2,2,3,2], 3))       # 3
# k=4, element appears once
print(single_number_k_times([1,1,1,1,7,2,2,2,2], 4))  # 7

Common XOR Interview Problems

Beyond the single-number family, XOR appears in these frequently-asked problems:

  • Find the Difference (LC 389): XOR all chars of both strings; the extra char remains
  • Hamming Distance (LC 461): XOR two numbers, count 1-bits in the result
  • Total Hamming Distance (LC 477): count 0s and 1s at each bit position across all pairs
  • XOR Queries of a Subarray (LC 1310): use prefix XOR array for range queries

In each case, XOR's cancellation property eliminates redundancy and reduces an O(n²) brute force to O(n).

# Find the difference between two strings
def find_the_difference(s, t):
    result = 0
    for c in s + t:
        result ^= ord(c)
    return chr(result)

print(find_the_difference('abcd', 'abcde'))  # 'e'

# Hamming distance: count differing bits
def hamming_distance(x, y):
    diff = x ^ y
    count = 0
    while diff:
        count += diff & 1
        diff >>= 1
    return count
    # or: bin(x ^ y).count('1')

print(hamming_distance(1, 4))   # 2: 001 vs 100 differ in bits 0 and 2
print(hamming_distance(3, 1))   # 1: 011 vs 001 differ in bit 1

# Prefix XOR for range queries
def xor_queries(arr, queries):
    prefix = [0] * (len(arr) + 1)
    for i, v in enumerate(arr):
        prefix[i+1] = prefix[i] ^ v
    return [prefix[r+1] ^ prefix[l] for l, r in queries]

print(xor_queries([1,3,4,8], [[0,1],[1,2],[0,3],[3,3]]))

Quick Check

Test your understanding of Data Structures & Algorithms — Coding Interview Prep concepts from this lesson.

Lesson Recap

In this lesson you learned: XOR's self-inverse property (a ^ a = 0) causes paired elements to cancel, leaving only the unique element when all numbers are XOR'd together, Single Number II uses bit-counting modulo 3 while Single Number III splits elements by the lowest differing bit, and XOR also solves missing number, find-the-difference, Hamming distance, and range XOR queries. Next up we explore bit masks for setting, clearing, toggling, and checking individual bits.

Frequently asked questions

Is the “Single Number and XOR Properties” lesson free?

Yes — the full text of “Single Number and XOR Properties” is free to read here on the web, and the DSA Interview Prep course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the DSA Interview Prep course, upgrade to CoddyKit PRO.

What will I learn in “Single Number and XOR Properties”?

Use XOR's self-inverse property to find the one element appearing once in a list where all others appear twice, then extend to single-number-II and III. You practise DSA Interview Prep with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start DSA Interview Prep?

No prior experience is required. DSA Interview Prep on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Single Number and XOR Properties” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this DSA Interview Prep lesson?

Yes. Every DSA Interview Prep lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Bitwise Operators: AND, OR, XOR, NOT, Shifts
  2. Single Number and XOR Properties
  3. Bit Masks: Set, Clear, Toggle, Check
  4. Counting Bits, Missing Number, and Reverse Bits
← Back to DSA Interview Prep