XOR (exclusive or) is one of the most useful operations in computing because of a single defining property: it is its own inverse. For any values, a ⊕ a = 0 and a ⊕ 0 = a, which means applying the same key twice undoes it (a ⊕ k ⊕ k = a). That reversibility makes XOR the natural tool for toggling and flipping bits, swapping two variables without a temporary, computing parity bits and simple checksums, rebuilding lost data in RAID arrays, mixing a keystream into plaintext in stream ciphers and the one-time pad, masking or inverting graphics, detecting which bits differ between two values, and mixing bits inside hash functions. It runs in a single CPU cycle, is commutative and associative, and appears everywhere from Ethernet frames to git object storage.
That paragraph is the summary an AI overview would give you. The rest of this article is the part it can't: the exact mechanics of why each trick works, verified code you can paste and run, a diagram of the encrypt-then-decrypt symmetry, and a live tool to XOR your own strings. One idea ties all of it together — every application below is a different way of exploiting the same self-inverse property.
XOR applications at a glance
| Application | How XOR is used | Why XOR fits |
|---|---|---|
| Bit toggling / flags | flags ^= MASK flips exactly the masked bits | x ^ 1 inverts a bit, x ^ 0 leaves it — perfect for toggle without reading state |
| XOR swap | a ^= b; b ^= a; a ^= b exchanges two values | Self-inverse + commutative: each value folds into the other and cancels back out |
| Parity bit / checksum | XOR all data bits into one parity bit | A single flipped bit changes the parity, so mismatches are detectable |
| RAID 3/4/5/6 redundancy | Store P = D1 ⊕ D2 ⊕ D3; recover a lost block by XORing the rest | Any one missing term is recoverable because XOR is reversible |
| Stream cipher / one-time pad | cipher = plaintext ⊕ keystream; decrypt by XORing the same keystream | p ⊕ k ⊕ k = p — the same operation both encrypts and decrypts |
| Bitmask / cursor rendering | Draw with pixel ^= color; draw again to erase | XORing the same color twice restores the original pixels, no backup needed |
| Change / difference detection | diff = a ⊕ b; count set bits for Hamming distance | Set bits mark exactly where the values differ; equal values XOR to 0 |
The XOR truth table
Everything above reduces to these four outputs. XOR returns 1 only when the two inputs differ:
| A | B | A ⊕ B |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
Read the corners: 0 ⊕ 0 = 0 and 1 ⊕ 1 = 0 are the two halves of a ⊕ a = 0. The middle rows show that XORing with 1 flips a bit while XORing with 0 (the top row's left input) leaves it. That is the entire mechanism.
Encrypt then decrypt: the self-inverse in one picture
The most consequential consequence of a ⊕ k ⊕ k = a is that one operation both scrambles and unscrambles. Feed plaintext and a key through XOR to get ciphertext; feed that ciphertext and the same key through XOR and the plaintext falls back out.
Notice the symmetry: there is no separate "decrypt" operation. Encryption and decryption are the identical XOR — the key is what makes it reversible. This is exactly why the security lives entirely in the key, not the algorithm.
Toggling, swapping, and finding the odd one out
Bit toggling is the everyday use. XORing a value against a mask flips only the masked bits:
#define FLAG_READ 0x01
#define FLAG_WRITE 0x02
#define FLAG_EXECUTE 0x04
int perms = FLAG_READ; // 0b001
perms ^= FLAG_WRITE; // toggle write on -> 0b011
perms ^= FLAG_WRITE; // toggle write off -> 0b001
Swapping without a temporary is the classic party trick — correct, but understand why before you use it:
// XOR swap: exchanges a and b with no temp
a ^= b; // a = a ^ b
b ^= a; // b = (a ^ b) ^ b = original a
a ^= b; // a = (a ^ b) ^ original_a = original b
It works because XOR is self-inverse and commutative. The one hazard: if a and b are the same location (aliased pointers), the first two steps zero it out. In practice a plain temporary swap is clearer and usually faster on modern CPUs, so the XOR version is mostly a teaching tool.
Finding the missing number turns a ⊕ a = 0 into an O(n) time, O(1) space algorithm. XOR every element together with every index — matched pairs cancel, and only the loner survives:
def find_missing(nums):
result = 0
for x in nums: # XOR the present values
result ^= x
for i in range(1, len(nums) + 2): # XOR the full 1..n range
result ^= i
return result # duplicates cancel; the missing number remains
# [1, 2, 4, 5] with 3 missing -> returns 3
Parity, RAID, and the one-time pad
A parity bit is just the XOR of every data bit. Flip any single bit in transit and the recomputed parity no longer matches — a cheap single-bit error detector used in memory, serial links, and disk storage.
RAID scales that idea to whole disks. The parity block is the XOR of the data blocks in a stripe, and because XOR is reversible, a lost block is the XOR of everything that survives:
D1 = 10101010
D2 = 11001100
P = 01100110 # P = D1 XOR D2
# D1 dies. Rebuild it from the survivors:
D1 = P XOR D2
= 01100110 XOR 11001100
= 10101010 # recovered exactly
Stream ciphers and the one-time pad XOR a keystream into the plaintext. Done properly this is the only provably unbreakable cipher — but "properly" is a narrow condition:
def xor_cipher(data: bytes, key: bytes) -> bytes:
"""Symmetric: run it again with the same key to decrypt."""
return bytes(b ^ key[i % len(key)] for i, b in enumerate(data))
msg = b"attack at dawn"
key = b"a-truly-random-pad-as-long-as-msg"
cipher = xor_cipher(msg, key)
assert xor_cipher(cipher, key) == msg # self-inverse round-trip
Be precise about the security claim. XOR is information-theoretically unbreakable only as a true one-time pad: a key that is genuinely random, at least as long as the message, and never reused. A short repeating key reduces XOR to a Vigenere cipher that falls to frequency analysis, and reusing a keystream lets an attacker XOR two ciphertexts to cancel the key outright (c1 ⊕ c2 = p1 ⊕ p2). Plain XOR with a short or reused key is obfuscation, not encryption. Try both the correct and the broken cases below:
Detecting differences and mixing hashes
XORing two values leaves a result whose set bits mark exactly where they differ; counting those bits is the Hamming distance, used in error detection, DNA comparison, and similarity search:
def hamming_distance(x, y):
return bin(x ^ y).count("1") # set bits of the XOR = differing positions
hamming_distance(0b0001, 0b0100) # -> 2
Delta encoding exploits the same property for compression: store D1 and then D1 ⊕ D2 instead of D2, so near-identical records collapse to a mostly-zero difference that compresses well. And inside real hash functions (BLAKE, SHA-family) XOR is a core mixing step — it folds bits together fast while keeping every input bit able to affect the output.
The bottom line
XOR earns its place across computing not because it is clever but because it is reversible: a ⊕ a = 0 and a ⊕ 0 = a mean the same one-cycle operation can set or clear, hide or reveal, corrupt-check or rebuild. Toggle a flag, swap two registers, protect a RAID stripe, mix a one-time-pad keystream, or measure how far two values drift — each is the self-inverse property wearing a different hat. Just keep the cryptographic caveat straight: XOR is perfect secrecy only as a true one-time pad, and trivial to break in every shortcut around it.