Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Coding Interview Patterns  Module 17 – Bit manipulation

XOR tricks: unique and missing values

Find the value seen once, two unpaired values and a missing number with XOR: why equal values cancel, how one bit splits a pair, and when sums overflow.

  • Beginner
  • 25 minutes
  • Examples run with Python 3.14.8 and Pyodide 314.0.7
  • By MySmartCoPilot
Free core, Pro depth: 8 of 25 minutes free
Free: Why equal values cancel · The problem: the unpaired sock tag · The brute force, and what it costs · 4 more sections. With a pass: 3 in-depth parts and 3 interview answers, about 17 minutes.

What you will learn

  • Find the value that appears once when every other value appears twice
  • Separate two unpaired values with a bit on which they differ
  • Find a missing number with XOR where a sum would overflow
  • Prove the XOR solutions correct and write them in 14 languages
  • Extend the idea to values seen three times, ranges of numbers and subarrays

Before you start

XOR (exclusive or): A bitwise operation that gives 1 where two bits differ and 0 where they are equal. x ^ x is 0, x ^ 0 is x, and the order of a chain of XORs does not change its result. finds the value without a partner. Two equal values cancel, because x ^ x is 0, and XOR with 0 changes nothing, so the XOR of a whole list keeps only the values that appear an odd number of times, in whatever order they come. When every value appears twice except one, a single pass with one integer returns that value: O(n) time and O(1) extra memory, where counting needs a dictionary. The same cancellation finds two unpaired values after one split on a bit where they differ, and a number missing from 0 to n without the sum that can overflow a 32-bit integer.

Why equal values cancel

XOR compares two numbers bit by bit and keeps a 1 wherever they differ. On a single bit it is addition modulo 2, so the order and the grouping of a chain of XORs never change its result. Bit i of the XOR of a list is therefore 1 exactly when an odd number of the values have a 1 in bit i. A value that appears twice adds an even number to every column and leaves no trace:

The tags 9, 14, 3, 9 and 14 written in binary: the columns with an odd number of 1s spell 0011, which is 3, the tag seen once.The log, one bit column at a timeroletag8s4s2s1spair91001pair141110once30011pair91001pair1411101s in the column4233odd count?nonoyesyesXOR of the log: 0011 = 3

The log 9, 14, 3, 9, 14, one bit column at a time

Text description of the diagram

The figure writes each tag of the log as four bits, worth 8, 4, 2 and 1. The tag 9 is 1001 and the tag 14 is 1110; both appear twice and are marked as pairs. The tag 3, 0011, appears once and is highlighted.

Under the five rows, each column's 1s are counted: the 8s column has 4, the 4s column 2, the 2s column 3 and the 1s column 3. The two copies of a paired tag always add an even number to a column, so only the tag seen once can make a count odd. The 2s and 1s columns are odd, so the XOR of the whole log is 0011, which is 3, the unpaired tag.

In the figure, the 1s and 2s columns hold three 1s each and the others an even number, so the XOR is 0011, the tag 3.

Checkpoint

Check: Why equal values cancel under XOR

  1. Question 1 of 2 Why may the tags of the log arrive in any order?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: On each bit XOR is addition modulo 2, so the order and the grouping of the XORs never change the result

    XOR on a single bit is addition modulo 2, which is commutative and associative, so a chain of XORs gives the same result in every order. That is why the two copies of a tag cancel wherever they stand.

  2. Question 2 of 2 What is 5 ^ 8 ^ 5 ^ 8 ^ 5?

    Type a number.

    A hint

    Count how many times each value appears.

    Show the answer: Question 2 of 2

    Answer: 5

    8 appears twice and cancels. 5 appears three times: two copies cancel and one is left, so the result is 5. The XOR of a list keeps exactly the values that appear an odd number of times.

The problem: the unpaired sock tag

The problem

A laundry sews a numbered tag into every sock and scans each tag when the washing comes back. Socks travel in pairs, so each tag is logged twice, except for one sock whose partner went missing. Return the tag that appears once. Limits: 1 ≤ n ≤ 2,000,001 entries, tags from 0 to 2³¹ − 1, and the log arrives as a stream that is read once, with O(1) extra memory.

  • [9, 14, 3, 9, 14] gives 3.
  • [7] gives 7.
  • [1001, 52, 52] gives 1001.

Two more questions use the same idea. Two socks may lose their partners, and then both tags are wanted, smaller first. And a train’s coaches, numbered 0 to n, may come back with one number missing from the yard’s list.

The brute force, and what it costs

Comparing each tag with the whole log needs no extra memory but up to n² looks, 4 × 10¹² at n = 2,000,001. Counting the tags in a dictionary takes one pass instead, but stores an entry for every distinct tag:

Scanning and counting, with the work counted Python · brute/brute_force.py
# The unpaired tag by brute force, two ways: compare every tag with the whole log (no extra memory, but n * n looks),
# or count the tags in a dictionary (one pass, but one entry for every distinct tag).


def unpaired_by_scanning(log):
    """For each tag, count its copies in the whole log: O(n^2) time, O(1) extra memory."""
    looks = 0
    for tag in log:
        copies = 0
        for other in log:
            looks += 1
            copies += other == tag
        if copies == 1:
            return tag, looks
    raise ValueError("every tag has a partner")


def unpaired_by_counting(log):
    """Count every tag in a dictionary: O(n) time, O(n) extra memory."""
    counts = {}
    for tag in log:
        counts[tag] = counts.get(tag, 0) + 1
    lonely = [tag for tag, c in counts.items() if c == 1]
    return lonely[0], len(counts)


for log in ([9, 14, 3, 9, 14], [7], [1001, 52, 52]):
    tag, looks = unpaired_by_scanning(log)
    _, entries = unpaired_by_counting(log)
    print(f"{str(log):<18} unpaired {tag:>4}   scanning: {looks:>2} looks   dictionary: {entries} {'entry' if entries == 1 else 'entries'}")

n = 2_000_001
print(f"n = {n:,}: scanning takes up to {n * n:,} looks,")
print(f"and the dictionary holds {n // 2 + 1:,} entries")

Output

[9, 14, 3, 9, 14]  unpaired    3   scanning: 15 looks   dictionary: 3 entries
[7]                unpaired    7   scanning:  1 looks   dictionary: 1 entry
[1001, 52, 52]     unpaired 1001   scanning:  3 looks   dictionary: 2 entries
n = 2,000,001: scanning takes up to 4,000,004,000,001 looks,
and the dictionary holds 1,000,001 entries

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 brute_force.py

On a seeded log of 2,000,001 tags, Python’s tracemalloc measured a peak of 60 MiB for the dictionary, against less than 1 KiB for a running XOR:

The dictionary's memory, measured on two million tags Python · memory/dict_memory.py
# The memory the dictionary of counts needs for a log of 2,000,001 tags, measured with tracemalloc, against the one
# integer that a running XOR keeps. The log is random but seeded, so every run builds the same log.
import random
import tracemalloc

rng = random.Random(41)
pairs = 1_000_000
tags = rng.sample(range(2**31), pairs + 1)
log = tags * 2
log.pop()  # the last tag keeps a single copy
rng.shuffle(log)

tracemalloc.start()
counts = {}
for tag in log:
    counts[tag] = counts.get(tag, 0) + 1
lonely = next(tag for tag, c in counts.items() if c == 1)
dict_peak = tracemalloc.get_traced_memory()[1]
tracemalloc.stop()
entries = len(counts)
del counts

tracemalloc.start()
acc = 0
for tag in log:
    acc ^= tag
xor_peak = tracemalloc.get_traced_memory()[1]
tracemalloc.stop()

print(f"log of {len(log):,} tags; the unpaired tag is {lonely:,}")
print(f"dictionary: {entries:,} entries, peak {dict_peak / 2**20:.0f} MiB of extra memory")
print(f"running XOR: {acc:,}, peak {'under 1 KiB' if xor_peak < 1024 else f'{xor_peak:,} bytes'} of extra memory")

Output

log of 2,000,001 tags; the unpaired tag is 1,275,481,847
dictionary: 1,000,001 entries, peak 60 MiB of extra memory
running XOR: 1,275,481,847, peak under 1 KiB of extra memory

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 dict_memory.py

Checkpoint

Check: What scanning and counting cost

  1. One question The log of 2,000,001 tags arrives as a stream that is read once, with O(1) extra memory. Which approach meets both limits?

    Choose one answer.

    Show the answer: One question

    Answer: One pass that XORs every tag into one integer

    Only the XOR pass reads the stream once and keeps O(1) memory. Counting breaks the memory limit, and scanning or sorting break the single read.

The key idea: let the pairs cancel

XOR every tag into one integer that starts at 0. Each pair cancels wherever its two copies stand, so the integer ends as the unpaired tag. The running value in between means nothing on its own:

Step Tag Running XOR In binary
1 9 9 1001
2 14 7 0111
3 3 4 0100
4 9 13 1101
5 14 3 0011

Step through the same pass and watch each second copy undo the first:

Interactive · step by step One pass of XOR over the log [9, 14, 3, 9, 14] Step through the log. Each tag is XORed into one integer that starts at 0; the box on the right shows its value after each tag.
One pass of XOR over the log [9, 14, 3, 9, 14], step 6 of 6. Showing tag 9, tag 14, tag 3, tag 9 again, tag 14 again, running XOR (3 = 0011). In focus: tag 14 again, running XOR.tag 91001tag 141110tag 30011tag 9 again1001tag 14 again1110running XOR3 = 0011^ 14the unpaired tag

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. Start at 0. The running value starts at 0, because XOR with 0 changes nothing. One integer is all the memory the pass needs, however long the log is.
  2. Tag 9. 0 ^ 9 is 9, which is 1001 in binary.
  3. Tag 14. 1001 and 1110 differ in bits 0, 1 and 2, so 9 ^ 14 is 0111, which is 7. The value in between means nothing on its own.
  4. Tag 3. 0111 ^ 0011 is 0100, which is 4.
  5. The second 9. 0100 ^ 1001 is 1101, which is 13. The two 9s have now both been XORed in, so they cancel, and 13 is 14 ^ 3.
  6. The second 14. 1101 ^ 1110 is 0011, which is 3. Both pairs have cancelled, so what is left is the tag that appears once.

Key idea

One integer, XORed with every tag in any order, ends as the tag without a partner, because each pair cancels to 0.

Checkpoint

Check: One pass of XOR over the log

  1. One question The log is [6, 13, 6, 2, 13]. What is the running XOR after the first three tags?

    Type a number.

    A hint

    6 ^ 13 is 11. Then XOR the third tag.

    Show the answer: One question

    Answer: 13

    6 ^ 13 is 11 (0110 ^ 1101 = 1011), and 11 ^ 6 is 13, because the two 6s have cancelled. After the last two tags the value is 2, the tag seen once.

Two unpaired tags: split on one bit

When tags a and b both appear once, the XOR of the log is a ^ b. It is not 0, because a and b differ, and each 1 in it marks a bit where they differ. The expression x & -x keeps the lowest set bit: The lowest bit of a number that is 1. In two's complement x & -x keeps exactly that bit: 12 is 1100, so 12 & -12 is 4. of x, so 12 & -12 is 4. Split the log on that bit: both copies of a paired tag agree on it and stay together, while a and b fall into different groups. XOR one group to get one answer, and XOR that with a ^ b to get the other.

For [12, 9, 12, 30, 7, 9] the XOR is 30 ^ 7, which is 25, or 11001 in binary, so the split bit is bit 0. The odd tags 9, 9 and 7 give 7, and 25 ^ 7 gives 30. Step through the split:

Interactive · step by step Splitting the log [12, 9, 12, 30, 7, 9] on bit 0 Step through the split. The XOR of the whole log picks the bit, each tag goes to the group of its own bit 0, and each group XORs to one answer.
Splitting the log [12, 9, 12, 30, 7, 9] on bit 0, step 9 of 9. Showing 12, 9, 12, 30, 7, 9, XOR of the log (30 ^ 7 = 25 = 11001), bit 0 is 1 (XOR so far: 7), bit 0 is 0 (XOR so far: 30). In focus: bit 0 is 1, bit 0 is 0.120110090100112011003011110700111901001XOR of the log30 ^ 7 = 25 = 11001bit 0 is 1XOR so far: 7bit 0 is 0XOR so far: 307, and 25 ^ 7 = 30

The drawing shows the whole scene; its 9 steps are listed below.

Every step as text
  1. XOR of the log. The pairs 12 and 9 cancel, so the XOR of the whole log is 30 ^ 7, which is 11001 in binary, 25. It is not 0, because the two answers differ.
  2. Choose the bit. Each 1 in 11001 marks a bit where the two answers differ. The lowest is bit 0, which 25 & -25 = 1 finds, so the log is split by bit 0.
  3. 12. 12 is even, so its bit 0 is 0 and it goes to the second group.
  4. 9. 9 is odd, so it goes to the first group.
  5. The second 12. The second 12 has the same bit 0 as the first, so it lands in the same group and cancels it.
  6. 30. 30 is even, so it joins the second group.
  7. 7. 7 is odd, so it joins the first group, the group of the other answer.
  8. The second 9. The second 9 follows the first into the first group and cancels it, leaving 7.
  9. The two answers. Each group holds one answer, 7 and 30, and 25 ^ 7 = 30 confirms the second from the first. Smaller first, the answer is (7, 30).

Checkpoint

Check: Splitting two unpaired values on one bit

  1. Question 1 of 2 The log [12, 9, 12, 30, 7, 9] is split on bit 0. Which tags land in the group whose bit 0 is 1?

    Choose every answer that is right.

    Show the answer: Question 1 of 2

    Answer:

    • 7
    • 9

    The group of odd tags holds 9, 7 and 9, which XOR to 7; the even group holds 12, 12 and 30, which XOR to 30. The two copies of each pair always fall on the same side.

  2. Question 2 of 2 What is 40 & -40, the lowest 1 bit of 40?

    Type a number.

    A hint

    Write 40 in binary.

    Show the answer: Question 2 of 2

    Answer: 8

    40 is 101000 in binary, so its lowest 1 is bit 3, worth 8. x & -x keeps exactly that bit.

A missing number without a sum

The usual answer subtracts the list’s sum from n(n + 1)/2. In 32-bit integers the product n(n + 1) passes 2,147,483,647 from n = 46,341, while the answer is still small. XOR the numbers 0 to n with the list instead: every number present cancels against itself and the missing one is left, and no value on the way has more bits than n. Here it is on a small list:

Interactive · step by step The missing number of 0 to 4 in [3, 0, 4, 1] Step through the pairs. XOR the numbers 0 to 4 with the list, and each number that is present cancels against its own copy.
The missing number of 0 to 4 in [3, 0, 4, 1], step 6 of 6. Showing 0, 1, 2, 3, 4, list: 3, list: 0, list: 4, list: 1. In focus: 2.01234list: 3list: 0list: 4list: 1no partner: the missing number

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. Two rows of numbers. On the left are the numbers 0 to 4 that should be there; on the right is the list of 4 numbers that came back. XOR all nine together.
  2. 3 cancels. The 3 of the list meets the 3 of 0 to 4, and 3 ^ 3 is 0, so the pair leaves no trace, wherever it stands in the order.
  3. 0 cancels. The 0 of the list cancels the 0 of 0 to 4.
  4. 4 cancels. The 4 cancels in the same way. No value on the way has more bits than 4, so there is nothing to overflow.
  5. 1 cancels. The 1 of the list cancels the 1 of 0 to 4.
  6. What is left. Only 2 has no partner, so the XOR of all nine numbers is 2, the missing number. The loop does the same with the indexes 0 to 3, starting its XOR at n = 4.

The same cancellation answers four more questions:

  • Values seen three times: counting each bit modulo 3 finds the value seen once.
  • A missing and a repeated number: one XOR and one split give both.
  • The XOR of a range: the XOR of 0 to n repeats with period 4.
  • Subarrays with a given XOR: prefix XORs and a dictionary count them in one pass.

Checkpoint

Check: The missing number from 0 to n

  1. One question From which n does the product n(n + 1) pass 2,147,483,647, the largest 32-bit int?

    Type a number.

    Show the answer: One question

    Answer: 46341

    46,340 × 46,341 is 2,147,441,940, which still fits, but 46,341 × 46,342 is 2,147,534,622, just past the largest 32-bit int. The answer is still a small number, but the product on the way to it is not.

Practise: two unpaired IDs

Exercise · Medium · Python

The two unpaired IDs in constant memory

Write two_unpaired(ids). In ids, exactly two IDs appear once and every other ID appears exactly twice, in any order. Return the two IDs that appear once as a tuple, the smaller one first.

two_unpaired([12, 9, 12, 30, 7, 9])       # (7, 30)
two_unpaired([5, 8])                      # (5, 8)
two_unpaired([-1, 2147483647, 5, 5])      # (-1, 2147483647)

IDs are 32-bit signed integers, from −2,147,483,648 to 2,147,483,647, and there are at most 10⁶ of them. Use O(1) extra memory: a few integer variables, and no dictionary, set, Counter, sorted copy or other list built from ids. One sample test reads your code to check that, and another has a million IDs.

Starter code · two_unpaired.py

def two_unpaired(ids):
    """The two IDs that appear once in ids, smaller first; every other ID appears exactly twice."""
    # Replace this line with your code.
    return (0, 0)
The sample tests · test_two_unpaired.py
import ast
import random
from collections import Counter

import two_unpaired as solution
from two_unpaired import two_unpaired


def brute(ids):
    return tuple(sorted(i for i, c in Counter(ids).items() if c == 1))


def test_examples():
    """the three examples of the prompt"""
    assert two_unpaired([12, 9, 12, 30, 7, 9]) == (7, 30)
    assert two_unpaired([5, 8]) == (5, 8)
    assert two_unpaired([-1, 2147483647, 5, 5]) == (-1, 2147483647)


def test_order_and_neighbours():
    """the smaller ID comes first, whatever order the log has, including IDs that differ in one bit"""
    assert two_unpaired([8, 5]) == (5, 8)
    assert two_unpaired([3, 2]) == (2, 3)
    assert two_unpaired([0, 1, 4, 4]) == (0, 1)
    assert two_unpaired([6, 6, 2, 4]) == (2, 4)


def test_ends_of_the_range():
    """IDs at both ends of the 32-bit range, and two IDs that differ only in the sign bit"""
    assert two_unpaired([-2147483648, 2147483647]) == (-2147483648, 2147483647)
    assert two_unpaired([-2147483648, 0, 7, 7]) == (-2147483648, 0)
    assert two_unpaired([2147483647, -1, 2147483646, 2147483646]) == (-1, 2147483647)


def test_random_logs_against_counting():
    """1,500 random logs with negative and positive IDs, against counting with a Counter"""
    rng = random.Random(17)
    for _ in range(1500):
        ids = rng.sample(range(-50, 50), rng.randint(2, 10))
        log = ids[2:] * 2 + ids[:2]
        rng.shuffle(log)
        assert two_unpaired(log) == brute(log), f"two_unpaired({log})"


def test_a_million_ids():
    """1,000,000 IDs: 499,999 pairs and the two unpaired IDs"""
    rng = random.Random(3)
    ids = rng.sample(range(2**30), 500_001)
    log = ids[2:] * 2 + ids[:2]
    rng.shuffle(log)
    assert two_unpaired(log) == tuple(sorted(ids[:2]))


def test_constant_extra_memory():
    """the function builds nothing from ids: no comprehensions, dict, set, Counter, sorted, list or slices"""
    with open(solution.__file__, encoding="utf-8") as source:
        tree = ast.parse(source.read())
    used = []
    for node in ast.walk(tree):
        if isinstance(node, (ast.ListComp, ast.SetComp, ast.DictComp, ast.GeneratorExp, ast.Dict, ast.Set)):
            used.append("a comprehension, dict or set")
        elif isinstance(node, ast.Slice):
            used.append("a slice")
        elif isinstance(node, ast.Call) and getattr(node.func, "id", getattr(node.func, "attr", None)) in ("list", "dict", "set", "sorted", "Counter", "defaultdict", "tuple", "frozenset"):
            used.append(f"{getattr(node.func, 'id', getattr(node.func, 'attr', ''))}()")
    assert not used, "O(1) extra memory: remove " + ", ".join(sorted(set(used)))
A hint

XOR every ID together first. Each pair cancels, so what is left is a ^ b for the two IDs you want, and it is not 0 because they are different. A bit that is 1 in a ^ b is a bit where a and b differ. How could that bit split the IDs into two groups, each with one of the two answers in it?

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Programmer Calculator (Bitwise) Type 30 ^ 7 and then 25 & -25 at 32 bits to watch the split bit appear.

These questions cover the ideas above, starting with the one you have answered least well.

Practice that adapts to you

Practise this lesson

13 questions about 9 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 13 Which two facts make the pairs of a log cancel under XOR?

    Choose one answer.

    Show the answer: Question 1 of 13

    Answer: x ^ x is 0 and x ^ 0 is x, in any order

    x ^ x = 0 makes each pair vanish, x ^ 0 = x keeps the rest, and XOR does not care about order, so the XOR of a log is the XOR of its unpaired values.

  2. Question 2 of 13 A log holds 5 twice and 6 three times. What does the XOR of the whole log give?

    Choose one answer.

    Show the answer: Question 2 of 13

    Answer: 6

    The XOR keeps the values that appear an odd number of times. 5 appears twice and 6 three times, so the result is 6; with every other value seen three times, plain XOR would no longer find the single one.

  3. Question 3 of 13 What does counting the tags in a dictionary cost?

    Choose one answer.

    Show the answer: Question 3 of 13

    Answer: One pass, but memory for every distinct tag

    The dictionary trades the n² looks of the scan for memory: one pass, but one entry per distinct tag. The XOR pass keeps the single pass and needs one integer.

  4. Question 4 of 13 A log has 1,000 entries. Up to how many looks does comparing each tag with the whole log need?

    Type a number.

    Show the answer: Question 4 of 13

    Answer: 1000000 looks

    Each of the 1,000 tags may be compared with all 1,000 entries, so up to 1,000 × 1,000 = 1,000,000 looks: n². At 2,000,001 entries that is 4 × 10¹².

  5. Question 5 of 13 Every tag of the log [6, 13, 6, 2, 13] appears twice except one. What is 6 ^ 13 ^ 6 ^ 2 ^ 13?

    Type a number.

    Show the answer: Question 5 of 13

    Answer: 2

    XOR does not care about order, so group the equal tags: (6 ^ 6) ^ (13 ^ 13) ^ 2 is 0 ^ 0 ^ 2, which is 2. The values in between, such as 6 ^ 13 = 11, mean nothing on their own; only the final value is the answer.

  6. Question 6 of 13 When every tag appears twice except one, what does the running XOR hold after the last tag?

    Choose one answer.

    Show the answer: Question 6 of 13

    Answer: The tag that appears once

    The pairs cancel wherever they stand, so after the last tag only the unpaired tag is left: 3 for the log [9, 14, 3, 9, 14].

  7. Question 7 of 13 A log of 10⁹ tags arrives over the network and cannot be stored. Why does the XOR pass still work?

    Choose one answer.

    Show the answer: Question 7 of 13

    Answer: It keeps one integer and reads each tag once, so it never needs the log again

    A stream read once with O(1) memory is exactly what one running integer allows: each tag is XORed in and forgotten, and the order makes no difference.

  8. Question 8 of 13 Two tags a and b appear once and a ^ b is 0b10100. Which bit can split the log into two groups with one of them in each?

    Choose one answer.

    Show the answer: Question 8 of 13

    Answer: Bit 2 or bit 4: any bit that is 1 in a ^ b

    A 1 in a ^ b marks a position where a and b differ, so splitting on bit 2 or on bit 4 puts them in different groups. x & -x picks the lowest such bit, bit 2 here, only because it is quick to compute. Bit 0 and bit 5 are 0 in a ^ b: a and b agree there and would land in the same group.

  9. Question 9 of 13 What does x & -x keep of x?

    Choose one answer.

    Show the answer: Question 9 of 13

    Answer: Its lowest 1 bit and nothing else

    x & -x keeps the lowest 1 bit of x, which is why it gives a bit where the two unpaired tags differ when x is a ^ b.

  10. Question 10 of 13 A teammate splits the log on the highest 1 bit of a ^ b instead of the lowest. What happens?

    Choose one answer.

    Show the answer: Question 10 of 13

    Answer: It still works, because a and b differ on that bit too

    The split needs a bit on which a and b differ, and every 1 of a ^ b is such a bit. The lowest is chosen only because x & -x computes it in one step.

  11. Question 11 of 13 A list holds every number from 0 to n except one, so it has n numbers. Why does the XOR start from n and not from 0?

    Choose one answer.

    Show the answer: Question 11 of 13

    Answer: The indexes 0 to n − 1 bring every number except n into the XOR, so n has to be added once

    The loop XORs each index i, from 0 to n − 1, with each value. Together the indexes and the starting value must bring in every number from 0 to n exactly once, so that each number in the list cancels; the indexes stop at n − 1, so n starts the XOR. Starting from 0 returns the wrong answer for [1], where 0 is missing.

  12. Question 12 of 13 Why can the XOR of 0 to n with the list never overflow, where the sum formula can?

    Choose one answer.

    Show the answer: Question 12 of 13

    Answer: XOR never produces a value with more bits than its operands, so no value on the way passes n's width

    A sum carries into higher bits and n(n + 1) needs about twice as many bits as n. XOR carries nothing, so every value it produces fits in the width of n.

  13. Question 13 of 13 The numbers 0 to 4 came back as [2, 0, 1, 4]. What does the XOR of 0 to 4 with the list give?

    Type a number.

    A hint

    Pair each number of the list with its copy among 0 to 4.

    Show the answer: Question 13 of 13

    Answer: 3

    2, 0, 1 and 4 each cancel against their copy among 0 to 4, so only 3 is left: the missing number.

The three loops proved, in 14 languages, with the overflow traps Pro: opens with a Pro, Premium or Ultimate pass

Each loop rests on one fact that is easy to state and easy to break in a fixed-width language. Here each fact is proved, the three functions run in 14 languages, and the two overflow traps are reproduced.

In this part:

  • Why the XOR of the log is the unpaired tag, proved column by column
  • Why the split keeps every pair together, and why any differing bit works
  • The three functions in 14 languages
  • Checked against counting on 9,000 random logs
  • The missing number in 32 bits: when the sum formula breaks, measured

1 diagram · 4 runnable examples (Python) · about 17 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Triples, a missing and a repeated number, ranges and subarrays, each run Pro: opens with a Pro, Premium or Ultimate pass

Each variant keeps the cancellation and changes what is counted. All four run below on original inputs, with the reason each works and the trap that comes with it.

In this part:

  • Every other value three times: count each bit modulo 3
  • One number missing and one repeated, from 1 to n
  • XOR from 0 to n in O(1), and the XOR of a range
  • Subarrays with a given XOR, from prefix XORs
  • The variants and their traps

4 runnable examples (Python) · about 4 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Five plausible bugs reproduced, and a practice set with answers Pro: opens with a Pro, Premium or Ultimate pass

Most wrong XOR solutions come from a small change to a correct loop. Each bug below runs against a slow, obviously right answer until it fails, and a practice set follows.

In this part:

  • Five bugs and the smallest inputs that break them
  • A practice set with sample answers

1 runnable example (Python) · about 4 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Interview questions

  1. Fresher to mid · Warm-up

    Why does XORing every value of a list return the value that appears once, when every other value appears twice?

    XOR works on each bit position separately, and on one bit it is addition modulo 2. So bit i of the XOR of the list is 1 exactly when an odd number of the values have a 1 in bit i. The two copies of a paired value add 0 or 2 to that count, which never changes whether it is odd, so only the unpaired value decides each bit, and the result equals it. The same facts in the usual form: x ^ x is 0, x ^ 0 is x, and the order and grouping of the XORs do not matter, so the pairs cancel wherever they stand. That is O(n) time and O(1) extra memory, with no dictionary.

  2. Fresher to mid

    Your Java two_unpaired passes every sample test but fails a hidden test where the two unpaired IDs are -1 and 2147483647. What goes wrong, and how do you write the split so that it works in Java, JavaScript, C++ and Go?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Mid to senior

    A teammate finds the missing number of 0..n with n * (n + 1) / 2 minus the sum of the list, all in Java int. It passes a test with n = 100,000 but returns a huge negative number for n = 46,341. Explain both results, and say which fix you would ship.

    Model answer, Pro. First step and follow-ups

  4. Mid to senior

    Sensor readings are 32-bit signed integers arriving as a stream. Every reading appears exactly three times except one, which appears once. With O(1) memory and one pass, how do you find the single reading, and how does the method change if every other reading appears k times?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • x ^ x is 0 and x ^ 0 is x, in any order, so the XOR of a list keeps the values that appear an odd number of times.
  • One XOR pass finds the unpaired value in O(n) time and O(1) memory, where counting needs memory for every distinct value.
  • Two unpaired values differ in at least one bit, x & -x finds one, and splitting the log on it separates them.
  • The XOR of 0 to n with the list finds a missing number without a sum that can overflow.

Terms in this lesson

lowest set bit
The lowest bit of a number that is 1. In two's complement x & -x keeps exactly that bit: 12 is 1100, so 12 & -12 is 4.
XOR (exclusive or)
A bitwise operation that gives 1 where two bits differ and 0 where they are equal. x ^ x is 0, x ^ 0 is x, and the order of a chain of XORs does not change its result.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.