Lesson 45 of 50 advanced

Bit Manipulation — The Secret Language of Computers

Learn how computers really think — in ones and zeros — and use it to solve problems at lightning speed!

Open interactive version (quiz + challenge)

Real-world analogy

Imagine you have a row of light switches on a wall. Each switch can only be ON (1) or OFF (0). That row of switches IS a binary number! If you have 8 switches, you can represent any number from 0 to 255. Flipping switches is like changing bits. Turning a switch ON is like setting a bit. Checking if a switch is ON is like reading a bit. And the cool part? Computers can flip millions of switches in a single operation! Bit manipulation is like learning the secret language that computers speak natively — and once you learn it, you can solve some problems in just one line of code!

What is it?

Bit manipulation is the technique of working directly with the binary (base-2) representation of numbers using bitwise operators like AND, OR, XOR, and shifts. Since computers store everything in binary, these operations are incredibly fast — often solving in one step what would normally take loops or complex math. It is a secret weapon in competitive programming!

Real-world relevance

Bit manipulation is used everywhere in real computing. File permissions in Linux use bits (read=4, write=2, execute=1). Network IP addresses use bit masking to define subnets. Video game engines use bitflags to track which items a player has collected. Compression algorithms like ZIP use bit-level operations to shrink files. Even the RGB colors on your screen are stored as bits — 8 bits for red, 8 for green, 8 for blue!

Key points

Code example

#include <bits/stdc++.h>
using namespace std;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    // === Binary Representation ===
    int a = 13;  // Binary: 1101
    int b = 10;  // Binary: 1010
    cout << "a = " << a << " (binary: 1101)\n";
    cout << "b = " << b << " (binary: 1010)\n\n";

    // === Bitwise Operators ===
    cout << "a & b = " << (a & b) << " (AND: 1000 = 8)\n";
    cout << "a | b = " << (a | b) << " (OR:  1111 = 15)\n";
    cout << "a ^ b = " << (a ^ b) << " (XOR: 0111 = 7)\n";
    cout << "~a    = " << (~a) << " (NOT: flips all bits)\n\n";

    // === Shift Operators ===
    cout << "3 << 2 = " << (3 << 2) << " (multiply by 4)\n";
    cout << "20 >> 2 = " << (20 >> 2) << " (divide by 4)\n\n";

    // === Odd/Even Check ===
    for (int n = 1; n <= 6; n++) {
        cout << n << " is " << ((n & 1) ? "odd" : "even") << "\n";
    }
    cout << "\n";

    // === Check, Set, Clear, Toggle a bit ===
    int num = 10;  // Binary: 1010
    int k = 2;     // Check bit at position 2
    cout << "num = " << num << " (1010)\n";
    cout << "Bit " << k << " is: " << ((num >> k) & 1) << "\n";
    cout << "Set bit 0:    " << (num | (1 << 0)) << " (1011 = 11)\n";
    cout << "Clear bit 3:  " << (num & ~(1 << 3)) << " (0010 = 2)\n";
    cout << "Toggle bit 1: " << (num ^ (1 << 1)) << " (1000 = 8)\n\n";

    // === Power of 2 Check ===
    for (int n : {1, 2, 3, 4, 7, 8, 16, 15}) {
        bool isPow2 = (n > 0) && ((n & (n - 1)) == 0);
        cout << n << " is " << (isPow2 ? "" : "NOT ") << "a power of 2\n";
    }
    cout << "\n";

    // === XOR Trick: Find Unique Element ===
    vector<int> arr = {4, 1, 2, 1, 2};
    int unique_val = 0;
    for (int x : arr) unique_val ^= x;
    cout << "Array: 4 1 2 1 2\n";
    cout << "Unique element: " << unique_val << "\n";

    return 0;
}

Line-by-line walkthrough

  1. 1. We start by defining two numbers a = 13 (binary 1101) and b = 10 (binary 1010). We will use these to demonstrate all the bitwise operators.
  2. 2. a & b gives 8 (binary 1000) because AND only keeps bits where BOTH numbers have a 1. Only the leftmost bit (the 8s place) is 1 in both numbers.
  3. 3. a | b gives 15 (binary 1111) because OR turns on a bit if EITHER number has a 1 there. Since a and b together cover all 4 bit positions, the result is all 1s.
  4. 4. a ^ b gives 7 (binary 0111) because XOR turns on a bit where the two numbers DIFFER. The leftmost bit is the same (both 1), so it becomes 0. The other three bits differ, so they become 1.
  5. 5. 3 << 2 shifts the bits of 3 (binary 11) left by 2 positions, giving 1100 = 12. Shifting left by k positions multiplies by 2^k, so 3 * 4 = 12.
  6. 6. The odd/even check uses n & 1. This isolates the last bit. If it is 1, the number is odd. If it is 0, the number is even. This is faster than using the modulo operator!
  7. 7. To check bit k, we shift right by k positions and AND with 1: (num >> k) & 1. This moves bit k to the last position and checks if it is 1 or 0.
  8. 8. To set bit k, we OR with (1 << k). This creates a number with only bit k turned on, and ORing guarantees that bit becomes 1 without affecting other bits.
  9. 9. The power-of-2 check n & (n-1) == 0 works because powers of 2 have exactly one 1-bit (like 1000), and subtracting 1 flips that bit and all below it (giving 0111). ANDing gives 0.
  10. 10. The XOR trick for finding the unique element works because XOR is associative and a ^ a = 0. All paired elements cancel out, leaving only the element that appears once. Beautiful and efficient!

Spot the bug

#include <bits/stdc++.h>
using namespace std;

int main() {
    // Check if numbers are powers of 2
    vector<int> nums = {0, 1, 2, 3, 4, 8, 16, 15};

    for (int n : nums) {
        if ((n & (n - 1)) == 0) {
            cout << n << " is a power of 2\n";
        } else {
            cout << n << " is NOT a power of 2\n";
        }
    }
    return 0;
}
Need a hint?
Look at what happens when n is 0. Is 0 a power of 2? What does 0 & (0-1) equal? Think about the edge case!
Show answer
The bug is that the code reports 0 as a power of 2! When n = 0, the expression (0 & (0-1)) equals (0 & -1) which equals 0. So the condition (n & (n-1)) == 0 is true for n = 0, but 0 is NOT a power of 2. The fix is to add a check for n > 0: change the condition to 'n > 0 && (n & (n - 1)) == 0'. Always remember to handle edge cases like 0 and negative numbers when doing bit manipulation!

Explain like I'm 5

Imagine you have a row of light switches on your bedroom wall. Each switch can be ON or OFF — that is a bit! A number is just a pattern of ON and OFF switches. The number 5 is like having switches OFF, ON, OFF, ON — that is 0101! Now, AND is like asking 'are BOTH switches on?' for each pair. OR is 'is at LEAST one switch on?' XOR is 'is EXACTLY one switch on?' Computers are super fast at flipping switches, so using these tricks makes your code zoom like a race car!

Fun fact

The XOR swap trick lets you swap two variables WITHOUT using a temporary variable: a ^= b; b ^= a; a ^= b; This works because XOR is its own inverse. Old-school assembly programmers used this trick when memory was precious and every byte counted. Modern compilers are so smart that a regular swap is just as fast, but it is still a cool party trick to show your programmer friends!

Hands-on challenge

Write a program that reads a number and prints its binary representation using a loop and right shifts (without using bitset). Then implement a function that counts the number of 1-bits in a number (this is called the popcount or Hamming weight). Next, solve this classic problem: given an array where every element appears exactly twice except one element that appears once, find the unique element using XOR. Test with arrays like [3, 5, 3, 7, 5] — the answer should be 7. Bonus: can you solve it when every element appears THREE times except one? (Hint: count bits at each position!)

More resources

Open interactive version (quiz + challenge) ← Back to course: CP Zero to Hero