Loading content...
If AND is the strict gatekeeper and OR is the inclusive collector, then XOR is the difference detector. The exclusive or operator outputs a 1 precisely when the two input bits are different—one is 0 and the other is 1. When both inputs match (both 0 or both 1), XOR outputs 0.
This behavior makes XOR one of the most fascinating and versatile operators in computing. Its unique mathematical properties enable elegant solutions to problems that would otherwise require complex logic: toggling bits without knowing their current state, swapping values without temporary storage, detecting changes between two states, and forming the foundation of modern cryptography.
By the end of this page, you will master XOR's fundamental behavior, understand its remarkable mathematical properties (self-inverse, commutativity, associativity), and apply it to practical tasks including bit toggling, value swapping, duplicate detection, parity checking, and cryptographic operations. XOR is arguably the most powerful single bitwise operator—prepare to be impressed.
The bitwise XOR operator, typically represented by the ^ symbol (caret), performs an exclusive disjunction on each pair of corresponding bits. The result bit is 1 if and only if exactly one of the input bits is 1; if both inputs are the same, the result is 0.
Unlike OR which is inclusive (either or both), XOR is exclusive—it demands difference.
Think of XOR as a difference detector or disagreement operator. It outputs 1 when the inputs disagree (one says yes, one says no) and 0 when they agree (both yes or both no). This "detect difference" property is the key to all of XOR's magical applications.
Formal Definition:
Given two binary values A and B, the XOR operation is defined as:
A XOR B = 1 if A ≠ B
A XOR B = 0 if A = B
Alternatively, XOR can be expressed using AND and OR:
A XOR B = (A AND NOT B) OR (NOT A AND B)
A XOR B = (A OR B) AND NOT(A AND B)
In modular arithmetic, XOR is addition modulo 2:
A ⊕ B = (A + B) mod 2
This modular arithmetic interpretation is profound—XOR is literally addition in the binary field GF(2), making it fundamental to coding theory and cryptography.
| Bit A | Bit B | A XOR B | Explanation |
|---|---|---|---|
| 0 | 0 | 0 | Same (both 0) → no difference → 0 |
| 0 | 1 | 1 | Different → difference detected → 1 |
| 1 | 0 | 1 | Different → difference detected → 1 |
| 1 | 1 | 0 | Same (both 1) → no difference → 0 |
Key Insight from the Truth Table:
Notice the perfect balance: exactly half the outcomes are 1, half are 0. This symmetry has deep implications. XOR is its own inverse—applying it twice returns to the original value. This reversibility is the foundation of XOR-based encryption and many clever algorithms.
XOR possesses several mathematical properties that make it uniquely powerful. Understanding these properties is essential for recognizing when XOR can elegantly solve a problem.
A XOR A = 0 — XORing any value with itself produces zero. Every bit cancels out.A XOR 0 = A — XORing with zero preserves the original value. Zero is the identity element.A XOR B = B XOR A — Order doesn't matter.(A XOR B) XOR C = A XOR (B XOR C) — Grouping doesn't matter.A XOR B XOR B = A — XORing twice with the same value cancels out.A XOR 1 = NOT A (for single bit) — XORing with 1 toggles the bit.123456789101112131415161718192021222324
// Self-Inverse: A ^ A = 0const a = 0b11010110;console.log((a ^ a).toString(2)); // "0" // Identity: A ^ 0 = Aconsole.log((a ^ 0).toString(2)); // "11010110" // Commutativity: A ^ B = B ^ Aconst b = 0b10101010;console.log((a ^ b) === (b ^ a)); // true // Associativity: (A ^ B) ^ C = A ^ (B ^ C)const c = 0b00110011;console.log(((a ^ b) ^ c) === (a ^ (b ^ c))); // true // Cancellation: A ^ B ^ B = Aconsole.log(((a ^ b) ^ b) === a); // true — b cancels out! // This is HUGE: XOR is reversible!// If result = a ^ key, then a = result ^ keyconst key = 0b11111111;const encrypted = a ^ key;const decrypted = encrypted ^ key;console.log(decrypted === a); // true — we recovered a!Why These Properties Matter:
These properties combine to make XOR incredibly useful for:
Here's a powerful way to think about XOR: the second operand controls whether the first operand is inverted. Where B=0, A passes through unchanged. Where B=1, A is inverted. This "selective inversion" interpretation explains both bit toggling and XOR's role in cryptography.
The XOR gate is more complex than AND or OR gates, typically requiring a combination of simpler gates. Understanding this helps appreciate both the computational cost and the elegant behavior of XOR.
Building XOR from Basic Gates:
XOR can be constructed as:
A XOR B = (A OR B) AND NOT(A AND B)
This reads as: "A or B, but not both." The OR captures "at least one," while the AND-NOT removes the "both" case.
12345678910111213141516171819202122232425262728293031323334353637383940414243
Method 1: (A OR B) AND NOT(A AND B) A ────┬─────────┐ │ │ v v ┌─────────┐ ┌─────────┐ │ OR │ │ AND │ └────┬────┘ └────┬────┘ │ │ │ ┌────┴────┐ │ │ NOT │ │ └────┬────┘ │ │ v v ┌────────────────────┐ │ AND │ └─────────┬──────────┘ │ v OUTPUT B ────┴─────────┘ Method 2: (A AND NOT B) OR (NOT A AND B) A ─────┬───────────────────┐ │ │ ┌────┴────┐ ┌────┴────┐ │ NOT │ │ (A) │ └────┬────┘ └────┬────┘ │ │ v v ┌────────────┐ ┌────────────┐ │ AND(NOT A,B)│ │AND(A,NOT B)│ └──────┬─────┘ └──────┬─────┘ │ │ └─────────┬─────────┘ │ ┌────┴────┐ │ OR │ └────┬────┘ │ v OUTPUTGate Count and Performance:
In terms of gate delay (propagation time through the circuit):
However, modern processors include optimized XOR gates in their ALUs, so at the instruction level, XOR typically executes in the same single cycle as AND/OR. The complexity is hidden in hardware.
CMOS Implementation:
In CMOS (Complementary Metal-Oxide-Semiconductor) technology, XOR gates can be built more efficiently using transmission gates, achieving near-parity with AND/OR in terms of transistor count and speed.
XNOR (exclusive NOR) is the complement of XOR: it outputs 1 when inputs are the SAME and 0 when they differ. XNOR = NOT(XOR) = A if and only if B (equivalence). In some contexts, XNOR is called the equivalence gate or coincidence gate.
One of XOR's most practical applications is toggling bits—flipping a bit's value without knowing its current state. Where OR can only set bits (0→1 or 1→1) and AND can only clear bits (1→0 or 0→0), XOR provides the toggle operation (0→1 or 1→0).
The Toggle Principle:
Recall XOR's controlled inversion behavior:
A XOR 0 = A (bit unchanged)A XOR 1 = NOT A (bit flipped)This means XORing with a mask toggles bits where the mask has 1s and leaves bits unchanged where the mask has 0s.
123456789101112131415161718192021222324252627282930
// Toggle bit at position nfunction toggleBit(value: number, n: number): number { const mask = 1 << n; // Create mask with only bit n set return value ^ mask; // XOR toggles that bit} // Demonstrationlet flags = 0b10100101; // Starting valueconsole.log(flags.toString(2).padStart(8, '0')); // "10100101" // Toggle bit 2 (currently 1 → becomes 0)flags = toggleBit(flags, 2);console.log(flags.toString(2).padStart(8, '0')); // "10100001" // Toggle bit 2 again (currently 0 → becomes 1)flags = toggleBit(flags, 2);console.log(flags.toString(2).padStart(8, '0')); // "10100101" // Toggle bit 6 (currently 0 → becomes 1)flags = toggleBit(flags, 6);console.log(flags.toString(2).padStart(8, '0')); // "11100101" // Toggle multiple bits at oncefunction toggleBits(value: number, mask: number): number { return value ^ mask;} const multiMask = 0b00110011; // Toggle bits 0, 1, 4, 5const result = toggleBits(0b10101010, multiMask);console.log(result.toString(2).padStart(8, '0')); // "10011001"Practical Application: Toggle Light Switch State
Imagine a system controlling 8 lights, each represented by a bit. XOR makes it trivial to implement "toggle" functionality:
123456789101112131415161718192021222324252627282930313233343536
class LightController { private lights: number = 0b00000000; // All lights off // Toggle a single light toggle(lightIndex: number): void { this.lights ^= (1 << lightIndex); } // Toggle multiple lights (e.g., from button press) toggleMany(mask: number): void { this.lights ^= mask; } // Check if a specific light is on isOn(lightIndex: number): boolean { return (this.lights & (1 << lightIndex)) !== 0; } getState(): string { return this.lights.toString(2).padStart(8, '0'); }} const controller = new LightController();console.log(controller.getState()); // "00000000" — all off controller.toggle(0); // Toggle light 0controller.toggle(3); // Toggle light 3console.log(controller.getState()); // "00001001" controller.toggle(0); // Toggle light 0 again (turns off)console.log(controller.getState()); // "00001000" // Party mode: toggle all even lightscontroller.toggleMany(0b01010101);console.log(controller.getState()); // "01011101"Toggling without conditionals is valuable in hardware interfacing and embedded systems where branching is expensive. Instead of if (isSet) clear() else set(), you simply XOR. Fewer branches means simpler code and better performance.
One of the most famous tricks in programming is the XOR swap—exchanging two values without using a temporary variable. This works by exploiting XOR's self-cancellation property.
The Algorithm:
a = a ^ b // a now contains a XOR b
b = a ^ b // b = (a XOR b) XOR b = a (original a)
a = a ^ b // a = (a XOR b) XOR a = b (original b)
After these three operations, a and b have swapped values!
1234567891011121314151617181920212223242526272829303132333435
// The XOR Swap Algorithmfunction xorSwap(a: number, b: number): [number, number] { console.log(`Before: a = ${a}, b = ${b}`); // Step 1: a becomes a XOR b a = a ^ b; console.log(`After step 1 (a = a ^ b): a = ${a}, b = ${b}`); // Step 2: b becomes (a XOR b) XOR b = a b = a ^ b; console.log(`After step 2 (b = a ^ b): a = ${a}, b = ${b}`); // Step 3: a becomes (a XOR b) XOR a = b a = a ^ b; console.log(`After step 3 (a = a ^ b): a = ${a}, b = ${b}`); return [a, b];} // Testlet [x, y] = xorSwap(42, 99);console.log(`\nFinal: x = ${x}, y = ${y}`);// Before: a = 42, b = 99// After step 1: a = 121, b = 99// After step 2: a = 121, b = 42// After step 3: a = 99, b = 42// Final: x = 99, y = 42 // Let's trace through with binary:// a = 42 = 0b00101010// b = 99 = 0b01100011//// Step 1: a = 42 ^ 99 = 0b01001001 = 73// Step 2: b = 73 ^ 99 = 0b00101010 = 42 (original a!)// Step 3: a = 73 ^ 42 = 0b01100011 = 99 (original b!)Why It Works — Mathematical Proof:
Let's call the original values A and B.
a = a ^ b: a = A ⊕ Bb = a ^ b: b = (A ⊕ B) ⊕ B = A ⊕ (B ⊕ B) = A ⊕ 0 = Aa = a ^ b: a = (A ⊕ B) ⊕ A = (A ⊕ A) ⊕ B = 0 ⊕ B = BThe associativity and self-cancellation properties of XOR make this work.
Don't use XOR swap when the variables might be the same memory location! If a and b refer to the same variable, a = a ^ a = 0, and you lose the value. Also, modern compilers optimize temporary variables effectively, so XOR swap rarely offers real-world benefits—it's more of a demonstration of XOR's properties. Use it for interviews and understanding, not production code.
1234567891011121314151617181920212223242526
// DANGER: XOR swap with same referencefunction unsafeSwap(arr: number[], i: number, j: number): void { // If i === j, this zeroes out arr[i]! arr[i] ^= arr[j]; arr[j] ^= arr[i]; arr[i] ^= arr[j];} // SAFE: Check for same indexfunction safeXorSwap(arr: number[], i: number, j: number): void { if (i !== j) { arr[i] ^= arr[j]; arr[j] ^= arr[i]; arr[i] ^= arr[j]; }} // BEST: Just use a temporary variable (compilers optimize this)function standardSwap(arr: number[], i: number, j: number): void { const temp = arr[i]; arr[i] = arr[j]; arr[j] = temp;} // In many languages, you can use destructuring:// [a, b] = [b, a]; // Simple and clear!One of the most elegant XOR applications is solving the "single number" problem: Given an array where every element appears twice except for one element that appears once, find the unique element in O(n) time and O(1) space.
The Insight:
XOR all elements together. Duplicate pairs cancel out (a ⊕ a = 0), and XOR with 0 is identity. Only the unique element survives!
123456789101112131415161718192021222324252627282930313233
/** * Find the single element that appears only once * when all other elements appear twice. * * Time: O(n) — single pass * Space: O(1) — only using a single variable */function findSingleNumber(nums: number[]): number { let result = 0; for (const num of nums) { result ^= num; // XOR accumulate } return result;} // Why it works:// [4, 1, 2, 1, 2]// 0 ^ 4 = 4// 4 ^ 1 = 5// 5 ^ 2 = 7// 7 ^ 1 = 6 (1 cancels: 5^1 was in there, now 1^1=0, so 4^2=6)// 6 ^ 2 = 4 (2 cancels)// Result: 4 console.log(findSingleNumber([4, 1, 2, 1, 2])); // 4console.log(findSingleNumber([2, 2, 1])); // 1console.log(findSingleNumber([1])); // 1console.log(findSingleNumber([1, 1, 2, 2, 3, 4, 4])); // 3 // Works with negative numbers too!console.log(findSingleNumber([-1, 3, -1])); // 3Extended Problem: Two Unique Elements
What if there are TWO unique elements (appearing once each), and all others appear twice? XOR still helps, but with an additional insight:
123456789101112131415161718192021222324252627282930313233343536
/** * Find the two elements that appear only once * when all other elements appear twice. * * Key insight: XOR all → get xor of two uniques → * find a differing bit → partition and XOR separately */function findTwoSingleNumbers(nums: number[]): [number, number] { // Step 1: XOR all elements → result is unique1 XOR unique2 let xorAll = 0; for (const num of nums) { xorAll ^= num; } // Step 2: Find any bit that differs between unique1 and unique2 // Use the rightmost set bit (there must be at least one since they differ) const diffBit = xorAll & (-xorAll); // Isolate rightmost set bit // Step 3: Partition numbers by whether they have this bit set // Unique1 and Unique2 will be in different partitions let group0 = 0; let group1 = 0; for (const num of nums) { if ((num & diffBit) === 0) { group0 ^= num; // This group will leave unique1 } else { group1 ^= num; // This group will leave unique2 } } return [group0, group1];} console.log(findTwoSingleNumbers([1, 2, 1, 3, 2, 5])); // [3, 5] or [5, 3]console.log(findTwoSingleNumbers([1, 2])); // [1, 2]The XOR trick works because XOR is essentially counting occurrences modulo 2. Any even count (2, 4, 6...) of the same value cancels to 0. Only odd counts survive. This principle extends to finding elements appearing 1 time when others appear 3 times (using bit counting), but pure XOR handles the pairs case elegantly.
XOR is fundamental to parity calculations—determining whether a value has an even or odd number of 1-bits. This has profound applications in error detection and correction.
Parity Bit Concept:
A parity bit is an extra bit added to data to make the total count of 1-bits either even (even parity) or odd (odd parity). If a single bit flips during transmission, the parity check fails, alerting us to an error.
12345678910111213141516171819202122232425262728293031
/** * Compute the parity of a number (0 if even number of 1s, 1 if odd) * Uses XOR reduction: successively fold the number in half */function computeParity(n: number): number { // For 32-bit integers: n ^= n >> 16; // XOR upper 16 bits with lower 16 bits n ^= n >> 8; // XOR upper 8 bits with lower 8 bits n ^= n >> 4; // XOR upper 4 bits with lower 4 bits n ^= n >> 2; // XOR upper 2 bits with lower 2 bits n ^= n >> 1; // XOR the final 2 bits return n & 1; // Return the parity bit} // Alternative: XOR all bits one by one (slower but clearer)function computeParitySimple(n: number): number { let parity = 0; while (n !== 0) { parity ^= (n & 1); // XOR with lowest bit n >>>= 1; // Shift right (unsigned) } return parity;} console.log(computeParity(0b1010)); // 0 (even: two 1s)console.log(computeParity(0b1011)); // 1 (odd: three 1s)console.log(computeParity(0b1111)); // 0 (even: four 1s)console.log(computeParity(0b0001)); // 1 (odd: one 1) // Verify parity matchconsole.log(computeParity(0b1010) === computeParitySimple(0b1010)); // trueRAID Systems and XOR:
RAID (Redundant Array of Independent Disks) uses XOR for parity-based redundancy. In RAID 5, data is striped across disks with parity blocks computed via XOR. If any single disk fails, its data can be recovered by XORing the remaining disks.
Disk A: 0b10110010
Disk B: 0b01100111
Parity: 0b11010101 (A XOR B)
If Disk A fails:
Disk A = Parity XOR Disk B = 0b11010101 XOR 0b01100111 = 0b10110010 ✓
This works because of XOR's cancellation property!
1234567891011121314151617181920212223242526272829
// Simulating RAID 5 parityfunction computeParityBlock(blocks: number[]): number { return blocks.reduce((parity, block) => parity ^ block, 0);} function recoverBlock( parityBlock: number, remainingBlocks: number[]): number { // XOR parity with all remaining blocks // This cancels out the remaining blocks, leaving the missing one return remainingBlocks.reduce( (acc, block) => acc ^ block, parityBlock );} // Three data blocksconst diskA = 0b10110010;const diskB = 0b01100111;const diskC = 0b11001010; // Compute parityconst parity = computeParityBlock([diskA, diskB, diskC]);console.log(parity.toString(2).padStart(8, '0')); // Parity block // Simulate disk B failure — recover from others + parityconst recoveredB = recoverBlock(parity, [diskA, diskC]);console.log(recoveredB === diskB); // true — B recovered!While simple parity detects single-bit errors, more sophisticated techniques like CRC (Cyclic Redundancy Check) use XOR operations over polynomial arithmetic in GF(2) to detect burst errors and provide stronger guarantees. XOR is at the core of these algorithms.
XOR's self-inverse property makes it the cornerstone of symmetric encryption. Many encryption schemes (from simple ciphers to sophisticated stream ciphers like ChaCha20) fundamentally rely on XOR.
The One-Time Pad:
The one-time pad is the only known encryption method that is theoretically unbreakable (proven by Claude Shannon). It works by XORing the plaintext with a truly random key of the same length:
Ciphertext = Plaintext XOR Key
Plaintext = Ciphertext XOR Key
12345678910111213141516171819202122232425262728
// IMPORTANT: This is for educational purposes only!// Real cryptography requires proper randomness and key management. function xorCipher(data: Uint8Array, key: Uint8Array): Uint8Array { const result = new Uint8Array(data.length); for (let i = 0; i < data.length; i++) { // XOR each byte with corresponding key byte // If key is shorter, wrap around (repeating key — less secure!) result[i] = data[i] ^ key[i % key.length]; } return result;} // Text to bytes and backconst encode = (str: string) => new TextEncoder().encode(str);const decode = (bytes: Uint8Array) => new TextDecoder().decode(bytes); // Encryption and decryptionconst plaintext = encode("Secret Message!");const key = encode("MySecretKey12345"); const encrypted = xorCipher(plaintext, key);console.log("Encrypted:", Array.from(encrypted)); // Garbled bytes const decrypted = xorCipher(encrypted, key); // Same operation!console.log("Decrypted:", decode(decrypted)); // "Secret Message!"Why XOR Works for Encryption:
Limitations:
123456789101112131415161718192021222324
// If you encrypt two messages with the same key:// C1 = M1 XOR K// C2 = M2 XOR K// // Then C1 XOR C2 = (M1 XOR K) XOR (M2 XOR K) = M1 XOR M2// The key cancels out! An attacker can analyze M1 XOR M2. // Demonstrationconst key = encode("SECRET");const message1 = encode("HELLO!");const message2 = encode("WORLD!"); const cipher1 = xorCipher(message1, key);const cipher2 = xorCipher(message2, key); // Attacker XORs the ciphertextsconst leaked = new Uint8Array(cipher1.length);for (let i = 0; i < cipher1.length; i++) { leaked[i] = cipher1[i] ^ cipher2[i];} console.log("M1 XOR M2:", Array.from(leaked));// This reveals relationship between messages!// With statistical analysis, messages can be recoveredThe XOR cipher examples are educational. For real applications, always use well-audited cryptographic libraries (like libsodium, OpenSSL, or your platform's crypto APIs). Proper implementations handle key generation, key management, initialization vectors, and many subtle security concerns.
XOR is arguably the most powerful and mathematically interesting of the bitwise operators. Its unique properties enable elegant solutions to problems that would otherwise require complex conditional logic. Let's consolidate the key concepts:
A ^ A = 0 — any value XORed with itself is zero. This enables cancellation.A ^ 0 = A — zero is the identity element; XOR with 0 preserves the value.| Operation | Expression | Notes |
|---|---|---|
| Toggle bit n | value ^ (1 << n) | 0→1 or 1→0 |
| Toggle multiple bits | value ^ mask | Flip where mask has 1s |
| Find single unique | arr.reduce((a,b) => a^b) | Pairs cancel out |
| Swap a and b | a^=b; b^=a; a^=b; | Don't use if a===b |
| Check difference | a ^ b | Non-zero if different |
| Compute parity | n ^= n>>1; n&1 | 0=even, 1=odd 1-bits |
| Simple encrypt | data ^ key | Same op decrypts |
| Controlled NOT | a ^ b | b=1: invert a; b=0: keep a |
What's Next:
With XOR mastered, we'll explore the NOT operator—the unary operation that inverts all bits. NOT completes our foundation by providing the complement operation, essential for creating masks and understanding two's complement representation.
You now have deep mastery of the XOR bitwise operator—from its Boolean algebra foundations through its remarkable mathematical properties to practical applications in toggling, swapping, duplicate detection, parity calculation, and cryptography. XOR is one of the most versatile tools in your bit manipulation toolkit!