Skip to content

Data Structures

1 post with the tag “Data Structures”

Mastering XOR Magic: Essential Party Tricks for Every Programmer

The XOR operation, or exclusive OR, holds a deceptively simple yet profoundly powerful property: applying XOR with the same value twice restores the original. Mathematically, for any bits A and B, A XOR B XOR B = A. This idempotent behavior—where the operation is its own inverse—unlocks a treasure trove of clever programming hacks. Let’s explore these “party tricks” that demonstrate XOR’s elegance, from quick demos to data structure innovations.

To see this in action, fire up Python and test all bit combinations:

for a in range(2):
for b in range(2):
assert a ^ b ^ b == a, f"Failed for a={a}, b={b}"
print("XOR property holds for all 1-bit cases!")

Since it works per bit, it scales to entire integers. This foundation enables everything that follows.

XOR shines in symmetric encryption. Convert a message like “hello world” to integers, XOR each with a key (say, 69), and you’ve got ciphertext. Decrypt by XORing again with the same key:

def encrypt(message, key):
return ''.join(chr(ord(c) ^ key) for c in message)
msg = "hello world"
key = 69
encrypted = encrypt(msg, key)
decrypted = encrypt(encrypted, key)
print(decrypted) # Back to "hello world"
wrong_key_decrypt = encrypt(encrypted, 42) # Gibberish!

This is a toy example—vulnerable to frequency analysis and known-plaintext attacks. Never use it in production, but it’s a fantastic illustration of XOR’s reversibility.

Swapping Variables Without a Temp (Even in C)

Section titled “Swapping Variables Without a Temp (Even in C)”

Modern languages like Python allow a, b = b, a. In C, without multiple assignment, XOR does the heavy lifting:

#include <stdio.h>
int main() {
int a = 69, b = 420;
printf("Before: a=%d, b=%d\n", a, b);
a ^= b; // a = 69 ^ 420
b ^= a; // b = 420 ^ (69 ^ 420) = 69
a ^= b; // a = (69 ^ 420) ^ 69 = 420
printf("After: a=%d, b=%d\n", a, b);
return 0;
}

No extra variables needed! Compilers optimize anyway, but this bitwise dance is a classic interview flex. (Pro tip: Addition-based swaps like a += b; b = a - b; a -= b; exist too, but XOR avoids overflow.)

Detecting the Duplicate in an Unsorted Array

Section titled “Detecting the Duplicate in an Unsorted Array”

Given numbers 1 to 100 with one duplicate (array size 101), find it in O(n) time without sorting. XOR all expected numbers (1^2^…^100), then XOR with array elements. Unique pairs cancel; the duplicate remains:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
srand(time(NULL));
int arr[101]; // 1-100 + one dupe
// Populate randomly with dupe (omitted for brevity)
int x = 0;
for (int i = 1; i <= 100; ++i) x ^= i;
for (int i = 0; i < 101; ++i) x ^= arr[i];
printf("Duplicate: %d\n", x);
return 0;
}

Brilliant for its constant space and linear time. Interviews love it—though it reveals more about memorization than skill.

The XOR Linked List: Half the Pointer Overhead

Section titled “The XOR Linked List: Half the Pointer Overhead”

Doubly linked lists store prev and next pointers per node, doubling pointer memory. XOR them into one field (zord = prev ^ next), halving usage (payload excluded).

Node traversal: Start with prev = NULL, compute next = zord ^ prev, print/update prev = current.

Here’s a minimal C implementation:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>
typedef struct Node {
int value;
uintptr_t zord; // Size of pointer, XOR of prev ^ next
} Node;
Node* node_create(int value) {
Node* node = malloc(sizeof(*node));
memset(node, 0, sizeof(*node));
node->value = value;
return node;
}
typedef struct LinkedList {
Node* begin;
Node* end;
} LinkedList;
void list_append(LinkedList* list, int value) {
Node* new_node = node_create(value);
if (!list->end) { // Empty list
list->begin = list->end = new_node;
return;
}
// Link to end
list->end->zord ^= (uintptr_t)new_node;
new_node->zord = (uintptr_t)list->end;
list->end = new_node;
}
Node* node_next(Node** prev_ptr, Node* curr) {
Node* next = (Node*)(curr->zord ^ (uintptr_t)*prev_ptr);
*prev_ptr = curr;
return next;
}
int main() {
LinkedList list = {0};
for (int i = 5; i <= 10; ++i) {
list_append(&list, i);
}
Node* prev = NULL;
Node* it = list.begin;
do {
printf("%d ", it->value);
it = node_next(&prev, it);
} while (it);
printf("\n"); // 5 6 7 8 9 10
return 0;
}

Bonus: Start from end with prev = NULL for reverse traversal. Null endpoints simplify edge cases (zord == 0 means isolated).

These tricks, while not production staples, sharpen bitwise intuition. The property A XOR B XOR B = A (or x ^ 0 = x) is XOR’s superpower—commutative, associative, and self-inverse. Next coding interview, dazzle with it. Got more XOR hacks? The bit manipulation well runs deep.