On This Page

What Is Bit Manipulation?

Every integer your computer stores is just a sequence of bits โ€” binary digits, each either 0 or 1. The number 13 in binary is 1101. The number 255 is 11111111. Your computer doesn't think in decimal โ€” it thinks in binary, and every arithmetic operation is ultimately performed on bits.

Bit manipulation means operating on those individual bits directly using bitwise operators, rather than treating the number as a single value. Instead of asking "is this number odd?" with n % 2 == 1, you ask "is the last bit set?" with n & 1. Same answer, but the bitwise version compiles to a single CPU instruction.

Why bother? Three reasons:

Binary Number Refresher

The number 13 in binary is 1101. Reading right to left, each position represents a power of 2:

1ร—2โฐ + 0ร—2ยน + 1ร—2ยฒ + 1ร—2ยณ = 1 + 0 + 4 + 8 = 13

The Six Bitwise Operators

Where It Shows Up

How It Works

Essential Bit Tricks

These are the building blocks. Internalize them โ€” they come up in interviews constantly and compose into larger solutions.

Check if bit i is set: (n >> i) & 1 or n & (1 << i)
Shift the bit you care about to position 0, then AND with 1 to isolate it. Returns 1 if set, 0 if not.

Set bit i (turn on): n | (1 << i)
OR with a mask (a number with specific bits set for a specific purpose) that has only bit i set. OR with 1 sets the target bit; OR with 0 leaves everything else unchanged.

Clear bit i (turn off): n & ~(1 << i)
AND with a mask that has every bit EXCEPT i set (the complement of 1 << i). AND with 1 preserves; AND with 0 clears.

Toggle bit i (flip): n ^ (1 << i)
XOR with a mask. XOR with 1 flips the target bit. XOR with 0 leaves everything else alone.

Clear the lowest set bit: n & (n - 1)
This is the most important trick in bit manipulation. Subtracting 1 from n flips the lowest set bit and all bits below it. AND-ing with the original n clears that lowest bit. Example: 12 (1100) & 11 (1011) = 8 (1000). This is the foundation of Brian Kernighan's bit counting algorithm.

Isolate the lowest set bit: n & (-n)
In two's complement (the standard way computers represent negative numbers: flip all bits and add 1), -n flips all bits above the lowest set bit. The AND gives you just that lowest 1-bit as a power of 2. Example: 12 (1100) & -12 (...0100) = 4 (0100). Used in Binary Indexed Trees (Fenwick trees) to navigate the tree structure.

Check if power of 2: n > 0 and (n & (n - 1)) == 0
Powers of 2 have exactly one bit set (e.g., 8 = 1000). Clearing that one bit with n & (n-1) gives 0. The n > 0 guard handles the edge case where 0 would falsely pass.

Get all 1s mask: (1 << n) - 1
Creates a number with the lowest n bits all set to 1. Example: (1 << 4) - 1 = 15 = 1111โ‚‚. Useful for extracting the lower n bits of a value: value & ((1 << n) - 1).

๐Ÿ’ก XOR is your secret weapon XOR has three properties that make it magical: (1) a ^ a = 0 โ€” anything XOR'd with itself vanishes. (2) a ^ 0 = a โ€” XOR with zero is identity. (3) It's commutative (order doesn't matter) and associative (grouping doesn't matter). This means XOR-ing all elements in an array where every value appears twice except one will cancel all pairs and leave you with the unique value. That's LeetCode 136 in O(n) time, O(1) space. One line. One loop.

Two's Complement

Negative numbers in most systems use two's complement: to get -n from n, flip all bits (bitwise NOT) and add 1. So in 8 bits:

Two's complement is elegant because addition works the same for positive and negative numbers โ€” no special cases needed in hardware. It's why n & (-n) isolates the lowest bit: -n = ~n + 1, which propagates a carry through all trailing zeros and stops at the first 1.

Bitmask DP

A bitmask is an integer used to represent a set. Bit i is set (1) means "item i is included in the set." This lets you do dynamic programming (solving problems by breaking them into overlapping subproblems and caching results) where states represent subsets of n items.

Iterating over all subsets of n items is just a loop from 0 to 2โฟ - 1. Each integer in that range represents a unique subset. For n = 20, that's about 1 million states โ€” totally feasible for competitive programming. For n > 20, it becomes impractical (2ยฒโฐ โ‰ˆ 10โถ, but 2ยฒโต โ‰ˆ 33 million).

Common bitmask DP problems: Traveling Salesman (visit all cities with minimum cost), assignment problems (assign n tasks to n workers), "partition into k equal subsets."

Interactive Visualization

Enter two numbers and see their binary representation side by side. Click the operation buttons to apply AND, OR, XOR, NOT, or shifts and watch the bits transform in real time.

Bit Operations Lab

Common Mistakes

โš ๏ธ Operator precedence: bitwise vs comparison In most languages, bitwise operators have lower precedence than comparison operators. So n & 1 == 0 is parsed as n & (1 == 0) = n & 0 = 0. Always parenthesize: (n & 1) == 0. This bug is silent and maddening โ€” the code compiles fine and gives wrong results.
โš ๏ธ Python's integers are arbitrary precision Python doesn't have 32-bit integers. ~0 in Python is -1, not 0xFFFFFFFF. Left-shifting creates bigger and bigger numbers instead of overflowing. For problems that assume 32-bit behavior (like "reverse bits of a 32-bit integer"), you need to mask explicitly: result & 0xFFFFFFFF. This bites every Python programmer at least once.
โš ๏ธ Shifting by too many bits is undefined In C/C++, shifting a 32-bit integer by 32 or more positions is undefined behavior โ€” the compiler can do anything, including silently returning 0 or the original value. In Java, shifts are modulo the type width (1 << 32 == 1 for int, not 0). Know your language's shift semantics. Guard with: if (i < 32) result |= (1 << i);.
โš ๏ธ Signed right shift vs logical right shift Arithmetic right shift (>> in most languages) fills the vacated leftmost bits with the sign bit (0 for positive, 1 for negative). Logical right shift (>>> in Java) always fills with 0. For negative numbers, >> keeps them negative, while >>> makes them positive. In Python, >> is always arithmetic, and there's no >>>. In C, behavior for negative values is implementation-defined.
โš ๏ธ Forgetting n > 0 check for power of 2 0 & (0 - 1) = 0 & (-1) = 0, so n & (n-1) == 0 is true for n = 0. But 0 is not a power of 2. Always include n > 0 in the check: n > 0 and (n & (n - 1)) == 0. Easy to forget, hard to debug.

Operations & Complexity

Operation Time Example Use Case
AND, OR, XOR, NOT O(1) a & b Masking, testing, toggling
Left/Right Shift O(1) a << 3 Multiply/divide by powers of 2
Count set bits (Kernighan's) O(k) k = set bits n & (n-1) loop Hamming weight, popcount
Count set bits (built-in) O(1) bin(n).count('1') Python shortcut; hardware popcount in C
Subset enumeration O(2โฟ) for mask in range(1< Bitmask DP, subset iteration
Enumerate subsets of a mask O(2^k) k = set bits sub = (sub-1) & mask Iterating only subsets of a given set

Every single bitwise operation is O(1) โ€” constant time, one or two CPU instructions. The whole point of bit manipulation is doing in O(1) what would otherwise take O(n). The complexity only grows when you loop over bits (like counting them with Kernighan's) or loop over subsets (bitmask DP). Even then, Kernighan's is O(k) where k is the number of set bits โ€” much better than checking all 32 positions.

Implementation

Common Bit Utilities

Python
def count_set_bits(n):
    """Count 1-bits using Brian Kernighan's trick.
    
    Each iteration clears the lowest set bit with n &= n - 1.
    We loop exactly as many times as there are 1-bits.
    Much better than checking all 32 positions one by one.
    
    Example: n = 13 (1101)
      Iter 1: 1101 & 1100 = 1100 (cleared bit 0, count=1)
      Iter 2: 1100 & 1011 = 1000 (cleared bit 2, count=2)
      Iter 3: 1000 & 0111 = 0000 (cleared bit 3, count=3)
      Done! 3 set bits.
    """
    count = 0
    while n:
        n &= n - 1  # Clear lowest set bit
        count += 1
    return count

def is_power_of_two(n):
    """Powers of 2 have exactly one bit set (e.g., 8 = 1000).
    n & (n-1) clears that one bit โ€” if the result is 0, 
    there was only one bit to begin with.
    n > 0 guard: 0 is NOT a power of 2.
    """
    return n > 0 and (n & (n - 1)) == 0

def single_number(nums):
    """Find the element that appears once (all others appear twice).
    
    XOR all elements: duplicates cancel (a ^ a = 0), leaving the unique value.
    O(n) time, O(1) space. No hash set needed. No sorting.
    
    Why it works: XOR is commutative and associative, so order doesn't matter.
    [2, 3, 2] โ†’ 2^3^2 = (2^2)^3 = 0^3 = 3
    """
    result = 0
    for num in nums:
        result ^= num
    return result

def get_ith_bit(n, i):
    """Check if bit i is set (0-indexed from right, i=0 is LSB)."""
    return (n >> i) & 1

def set_ith_bit(n, i):
    """Set bit i to 1 (turn it on)."""
    return n | (1 << i)

def clear_ith_bit(n, i):
    """Clear bit i to 0 (turn it off)."""
    return n & ~(1 << i)

def toggle_ith_bit(n, i):
    """Flip bit i (0โ†’1 or 1โ†’0)."""
    return n ^ (1 << i)

def lowest_set_bit(n):
    """Isolate the lowest set bit as a power of 2.
    n & (-n) works because -n = ~n + 1 in two's complement.
    Example: 12 (1100) โ†’ 4 (0100)
    Used in Fenwick trees (Binary Indexed Trees) for tree navigation.
    """
    return n & (-n)

def swap_without_temp(a, b):
    """Swap two values using XOR โ€” no temporary variable.
    
    Step 1: a ^= b  โ†’ a now holds a^b
    Step 2: b ^= a  โ†’ b = b ^ (a^b) = a  (b is now original a)
    Step 3: a ^= b  โ†’ a = (a^b) ^ a = b  (a is now original b)
    
    Cute trick, but modern compilers optimize regular swaps just as well.
    Don't use this when a and b point to the same memory location โ€”
    a ^= a gives 0, not a swap!
    """
    a ^= b
    b ^= a
    a ^= b
    return a, b
JavaScript
function countSetBits(n) {
  // Brian Kernighan's: loop once per set bit
  let count = 0;
  while (n) {
    n &= n - 1;
    count++;
  }
  return count;
}

function isPowerOfTwo(n) {
  return n > 0 && (n & (n - 1)) === 0;
}

function singleNumber(nums) {
  // XOR everything: duplicates cancel, unique value survives
  return nums.reduce((acc, n) => acc ^ n, 0);
}

// Enumerate all subsets of a given bitmask
// e.g., subsets of 0b1010 = [1010, 1000, 0010, 0000]
function enumerateSubsets(mask) {
  const subsets = [];
  let sub = mask;
  while (sub > 0) {
    subsets.push(sub);
    sub = (sub - 1) & mask; // Next smaller subset of mask
  }
  subsets.push(0); // Empty subset
  return subsets;
}

// Reverse bits of a 32-bit unsigned integer
function reverseBits(n) {
  let result = 0;
  for (let i = 0; i < 32; i++) {
    result = (result << 1) | (n & 1);  // Push LSB of n onto result
    n >>= 1;                            // Move to next bit of n
  }
  return result >>> 0;  // Convert to unsigned 32-bit
}

Bitmask Subset Operations (JavaScript)

JavaScript
// === Bitmask operations for subset-based problems ===

// Check/set/clear/toggle bit at position i
const getBit    = (mask, i) => (mask >> i) & 1;
const setBit    = (mask, i) => mask | (1 << i);
const clearBit  = (mask, i) => mask & ~(1 << i);
const toggleBit = (mask, i) => mask ^ (1 << i);

// Count set bits (Kernighan's)
function popcount(n) {
  let count = 0;
  while (n) { n &= n - 1; count++; }
  return count;
}

// Isolate lowest set bit: 1100 โ†’ 0100
const lowestBit = (n) => n & (-n);

// Check power of 2
const isPow2 = (n) => n > 0 && (n & (n - 1)) === 0;

// Generate all submasks of a given mask
// e.g., submasks of 0b101 = [0b101, 0b100, 0b001, 0b000]
function* submasks(mask) {
  let sub = mask;
  while (sub > 0) {
    yield sub;
    sub = (sub - 1) & mask;
  }
  yield 0;
}

// Iterate over all masks with exactly k bits set out of n
// (Gosper's hack: efficiently finds the next combination)
function* combinations(n, k) {
  let mask = (1 << k) - 1; // First combination: lowest k bits
  const limit = 1 << n;
  while (mask < limit) {
    yield mask;
    // Gosper's hack: compute the next combination
    const c = mask & (-mask);        // lowest set bit
    const r = mask + c;              // carry the lowest bit
    mask = (((r ^ mask) >> 2) / c) | r;
  }
}

// Example: all ways to choose 2 items from 4
for (const combo of combinations(4, 2)) {
  console.log(combo.toString(2).padStart(4, '0'));
}
// 0011, 0101, 0110, 1001, 1010, 1100

Two Unique Numbers (XOR Trick Extended)

Python
def single_number_iii(nums):
    """Find TWO numbers that each appear once (all others appear twice).
    
    LeetCode 260. Can't just XOR everything โ€” you'd get a^b, not a and b separately.
    
    Key insight: a^b has at least one bit set (since a โ‰  b). Use any set bit
    to partition the array into two groups: numbers with that bit set, and
    numbers without. Each group contains exactly one unique number.
    XOR each group separately to find them.
    
    O(n) time, O(1) space.
    """
    # Step 1: XOR everything โ†’ gives a^b
    xor_all = 0
    for num in nums:
        xor_all ^= num
    
    # Step 2: Find any bit where a and b differ
    # (lowest set bit of xor_all is easiest to grab)
    diff_bit = xor_all & (-xor_all)
    
    # Step 3: Partition into two groups and XOR each
    a, b = 0, 0
    for num in nums:
        if num & diff_bit:
            a ^= num   # Group 1: this bit is set
        else:
            b ^= num   # Group 2: this bit is not set
    
    return [a, b]
JavaScript
function singleNumberIII(nums) {
  // XOR everything โ†’ a ^ b
  let xorAll = 0;
  for (const num of nums) xorAll ^= num;

  // Find a bit where a and b differ
  const diffBit = xorAll & (-xorAll);

  // Partition and XOR each group
  let a = 0, b = 0;
  for (const num of nums) {
    if (num & diffBit) a ^= num;
    else b ^= num;
  }
  return [a, b];
}

// singleNumberIII([1,2,1,3,2,5]) โ†’ [3, 5]

Bitmask DP: Traveling Salesman

Python
def tsp(dist):
    """Traveling Salesman via bitmask DP. O(2^n ร— nยฒ) time.
    
    The classic NP-hard problem: visit every city exactly once and return
    to the start, minimizing total distance. For small n (โ‰ค 20),
    bitmask DP is practical.
    
    dist[i][j] = cost to travel from city i to city j.
    
    State: dp[mask][i] = minimum cost to have visited exactly the cities
           in 'mask' (a bitmask) and be currently at city i.
    
    Transition: to extend from city u to unvisited city v:
           dp[mask | (1<

Real-World Example: Unix File Permissions

Every file on a Unix/Linux/macOS system has a permissions bitmask. When you run ls -l and see -rwxr-xr--, those 9 characters represent 9 bits packed into a single number. When you run chmod 755, you're directly writing a bitmask.

Python
"""Unix file permissions are pure bit manipulation in practice.

Each permission is one bit:
  Read (r)    = 4 = 100 in binary
  Write (w)   = 2 = 010 in binary
  Execute (x) = 1 = 001 in binary

Three groups of 3 bits each: owner | group | others
  chmod 755 = 111 101 101
            = rwx r-x r-x
            = owner can do everything, group/others can read+execute
"""

# Permission bits (each is a power of 2 โ€” one bit)
READ    = 0b100  # 4
WRITE   = 0b010  # 2
EXECUTE = 0b001  # 1

def parse_permissions(octal_mode):
    """Convert chmod number to human-readable permissions string.
    
    chmod 755 โ†’ owner=rwx, group=r-x, others=r-x
    This is just reading individual bits from a bitmask.
    """
    result = []
    for shift, name in [(6, "owner"), (3, "group"), (0, "others")]:
        bits = (octal_mode >> shift) & 0b111  # Extract 3 bits for this group
        perms = ""
        perms += "r" if bits & READ else "-"      # Bit 2 set?
        perms += "w" if bits & WRITE else "-"      # Bit 1 set?
        perms += "x" if bits & EXECUTE else "-"    # Bit 0 set?
        result.append(f"{name}={perms}")
    return ", ".join(result)

def check_access(file_mode, user_type, action):
    """Check if a user type has a specific permission.
    
    user_type: 'owner', 'group', or 'others'
    action: READ, WRITE, or EXECUTE
    
    This is what the OS kernel does on every file access โ€” 
    extract the relevant 3 bits and check with AND.
    """
    shifts = {"owner": 6, "group": 3, "others": 0}
    user_bits = (file_mode >> shifts[user_type]) & 0b111
    return bool(user_bits & action)

def grant_permission(file_mode, user_type, action):
    """Add a permission using OR (set the bit)."""
    shifts = {"owner": 6, "group": 3, "others": 0}
    return file_mode | (action << shifts[user_type])

def revoke_permission(file_mode, user_type, action):
    """Remove a permission using AND + NOT (clear the bit)."""
    shifts = {"owner": 6, "group": 3, "others": 0}
    return file_mode & ~(action << shifts[user_type])

# Demo
mode = 0o755  # Common permission: rwxr-xr-x
print(parse_permissions(mode))
# "owner=rwx, group=r-x, others=r-x"

print(check_access(mode, "group", WRITE))   # False โ€” group can't write
print(check_access(mode, "owner", EXECUTE)) # True โ€” owner can execute

mode = grant_permission(mode, "group", WRITE)
print(f"After granting group write: {oct(mode)}")  # 0o775
print(parse_permissions(mode))
# "owner=rwx, group=rwx, others=r-x"

This isn't an analogy โ€” this is literally how Unix permissions work. The kernel uses bitwise AND to check permissions on every single file access. Billions of times per second across every Linux server on the planet. Bit manipulation at its most practical.

More Real-World Applications

1. Feature Flags in Software

Modern software systems use feature flags (also called feature toggles) to enable or disable functionality for specific users, environments, or rollout percentages. When you need to check dozens of flags per request, a bitmask is orders of magnitude faster than a dictionary lookup.

JavaScript
// Feature flags as a bitmask โ€” one integer replaces a whole config object
const FEATURES = {
  DARK_MODE:       1 << 0,  // 0b0001
  BETA_UI:         1 << 1,  // 0b0010
  ANALYTICS:       1 << 2,  // 0b0100
  PREMIUM_CONTENT: 1 << 3,  // 0b1000
  NEW_CHECKOUT:    1 << 4,  // 0b10000
  AI_ASSISTANT:    1 << 5,  // 0b100000
};

// User's features stored as a single integer (fits in one DB column)
let userFlags = FEATURES.DARK_MODE | FEATURES.ANALYTICS;  // 0b0101 = 5

// Check if a feature is enabled โ€” single AND operation, one CPU cycle
function hasFeature(flags, feature) {
  return (flags & feature) !== 0;
}

// Enable a feature โ€” OR sets the bit
function enableFeature(flags, feature) {
  return flags | feature;
}

// Disable a feature โ€” AND with NOT clears the bit
function disableFeature(flags, feature) {
  return flags & ~feature;
}

// Toggle a feature โ€” XOR flips the bit
function toggleFeature(flags, feature) {
  return flags ^ feature;
}

console.log(hasFeature(userFlags, FEATURES.DARK_MODE));   // true
console.log(hasFeature(userFlags, FEATURES.BETA_UI));     // false

userFlags = enableFeature(userFlags, FEATURES.PREMIUM_CONTENT);
console.log(userFlags.toString(2));  // "1101" โ€” bits 0, 2, 3 set

// Why bitmasks over objects? In a hot path (middleware checking flags
// on every request), one integer comparison is 100x faster than
// property lookups on an object. At 10,000 requests/sec, it adds up.

2. Color Manipulation in Graphics

Colors on screen are typically stored as 32-bit integers: 8 bits each for Alpha, Red, Green, Blue (ARGB). Extracting, blending, and transforming colors is pure bit manipulation.

Python
def extract_rgba(color):
    """Extract individual color channels from a 32-bit ARGB integer.
    
    0xFF3366AA โ†’ A=255, R=51, G=102, B=170
    Each channel is 8 bits. Shift right to position, mask with 0xFF.
    """
    a = (color >> 24) & 0xFF  # Shift right 24, mask 8 bits
    r = (color >> 16) & 0xFF
    g = (color >> 8) & 0xFF
    b = color & 0xFF
    return a, r, g, b

def pack_rgba(a, r, g, b):
    """Pack four 8-bit channels into a single 32-bit integer."""
    return (a << 24) | (r << 16) | (g << 8) | b

def alpha_blend(fg, bg):
    """Blend foreground over background using alpha compositing.
    
    This is what happens billions of times per frame when your
    browser composites translucent elements, drop shadows, and
    overlapping windows. GPUs do this in parallel across millions
    of pixels simultaneously.
    """
    fa, fr, fg_r, fb = extract_rgba(fg)
    _, br, bg_r, bb = extract_rgba(bg)
    
    alpha = fa / 255.0
    out_r = int(fr * alpha + br * (1 - alpha))
    out_g = int(fg_r * alpha + bg_r * (1 - alpha))
    out_b = int(fb * alpha + bb * (1 - alpha))
    
    return pack_rgba(255, out_r, out_g, out_b)

# Example: semi-transparent red over white
red_50 = pack_rgba(128, 255, 0, 0)   # 50% transparent red
white = pack_rgba(255, 255, 255, 255)  # solid white
blended = alpha_blend(red_50, white)
print(extract_rgba(blended))  # (255, 255, 127, 127) โ€” pinkish

3. Bloom Filters โ€” Probabilistic Set Membership

A Bloom filter is a space-efficient data structure that answers "is this element in the set?" using a bit array and multiple hash functions. It can have false positives (saying "yes" when the answer is "no") but never false negatives. It's used in databases (Cassandra, PostgreSQL), caches (CDNs), and spell checkers.

Python
class BloomFilter:
    """A simple Bloom filter โ€” a bit array with multiple hash functions.
    
    Used by Chrome to check URLs against a malware list (locally,
    before sending the URL to Google's servers). Cassandra uses them
    to avoid disk reads for keys that definitely don't exist.
    
    Space: a few KB for millions of entries with <1% false positive rate.
    A hash set for the same data would use megabytes.
    """
    def __init__(self, size=1024, num_hashes=3):
        self.size = size
        self.num_hashes = num_hashes
        self.bit_array = 0  # Single integer as a bit array!
    
    def _hashes(self, item):
        """Generate multiple hash positions using double hashing."""
        h1 = hash(item)
        h2 = hash(str(item) + "salt")
        for i in range(self.num_hashes):
            yield (h1 + i * h2) % self.size
    
    def add(self, item):
        """Set the bits at each hash position."""
        for pos in self._hashes(item):
            self.bit_array |= (1 << pos)  # Set bit with OR
    
    def might_contain(self, item):
        """Check if ALL hash positions are set.
        
        If any bit is 0, the item is DEFINITELY not in the set.
        If all bits are 1, the item PROBABLY is (could be false positive).
        """
        for pos in self._hashes(item):
            if not (self.bit_array & (1 << pos)):  # Check bit with AND
                return False  # Definitely not present
        return True  # Probably present (could be false positive)

bf = BloomFilter(size=64, num_hashes=3)
bf.add("hello")
bf.add("world")
print(bf.might_contain("hello"))   # True (definitely added)
print(bf.might_contain("world"))   # True (definitely added)
print(bf.might_contain("python"))  # Probably False (unless collision)

4. Network Subnet Calculations

Every time your computer connects to a network, the operating system uses bitwise AND to determine if a destination IP is on the local subnet or needs to be routed through a gateway. This happens for every single packet โ€” it's one of the most frequent bitwise operations in computing.

JavaScript
function ipToInt(ip) {
  // "192.168.1.100" โ†’ 32-bit integer
  // Each octet is 8 bits, packed left to right
  const parts = ip.split('.').map(Number);
  return ((parts[0] << 24) | (parts[1] << 16) |
          (parts[2] << 8) | parts[3]) >>> 0;
}

function intToIp(n) {
  return [
    (n >>> 24) & 0xFF,
    (n >>> 16) & 0xFF,
    (n >>> 8) & 0xFF,
    n & 0xFF
  ].join('.');
}

function cidrToMask(prefix) {
  // "/24" โ†’ 255.255.255.0 (the top 24 bits are 1)
  // ~0 is all 1s. Shift left by (32-prefix) to clear the lower bits.
  return (~0 << (32 - prefix)) >>> 0;
}

function getNetworkAddress(ip, prefix) {
  // AND the IP with the mask โ€” this is what routers do
  const ipInt = ipToInt(ip);
  const mask = cidrToMask(prefix);
  return intToIp((ipInt & mask) >>> 0);
}

function isOnSameSubnet(ip1, ip2, prefix) {
  // Two IPs are on the same subnet if their network addresses match
  return getNetworkAddress(ip1, prefix) === getNetworkAddress(ip2, prefix);
}

console.log(getNetworkAddress("192.168.1.100", 24));
// "192.168.1.0" โ€” network address

console.log(isOnSameSubnet("192.168.1.100", "192.168.1.200", 24));
// true โ€” same /24 subnet

console.log(isOnSameSubnet("192.168.1.100", "192.168.2.100", 24));
// false โ€” different /24 subnets

Interview Patterns

๐Ÿ’ก "Find the single/unique element" = XOR everything When every element appears twice except one, XOR the entire array. Pairs cancel (a^a = 0), leaving the unique value. O(n) time, O(1) space. For the variant where every element appears three times except one (LeetCode 137), count bits modulo 3 at each of the 32 bit positions โ€” the remainder at each position gives you the unique number's bits.
๐Ÿ’ก n & (n-1) is the Swiss Army knife This clears the lowest set bit. Use it to: (1) count set bits (loop until n = 0), (2) check power of 2 (result is 0?), (3) check if n has at most k set bits (apply k times, check if 0). It's the single most useful bit trick in interviews. Master it.
๐Ÿ’ก Bitmask = subset representation When n โ‰ค 20 and you need to explore all subsets, encode subsets as integers. Bit i set = item i included. Iterate from 0 to 2โฟ - 1. This pattern appears in TSP, assignment problems, "partition into k equal subsets," and "maximum students taking exam" โ€” any NP-hard problem where n is small enough for exponential search.
๐Ÿ’ก XOR for finding two unique numbers LeetCode 260: every element appears twice except two. XOR everything to get a^b. Find a set bit (where a and b differ). Partition the array by that bit. XOR each group separately to recover a and b. This upgrade from the basic XOR trick impresses interviewers because it shows deep understanding.
๐Ÿ’ก "Reverse bits" = divide and conquer on bit groups LeetCode 190: reverse a 32-bit integer. The elegant approach: swap adjacent bits, then swap pairs of bits, then nibbles (4-bit groups), then bytes, then halves. Each swap uses masks and shifts. It's O(log 32) = O(1) with constant-time operations. The iterative approach (loop 32 times, push LSB onto result) also works and is easier to code in interviews.
๐Ÿ’ก "Add two numbers without +" = XOR + AND + shift LeetCode 371: XOR gives the sum without carries. AND gives the carry bits (both were 1). Left-shift the carry by 1 (carry propagates to the next position). Repeat until carry is 0. This is literally how hardware adders work โ€” your CPU's arithmetic unit chains this logic across 64 bits in parallel. In Python, you need to mask to 32 bits because integers are arbitrary precision.
๐Ÿ’ก "Number of 1 Bits" variations: Hamming weight and distance Hamming weight is the count of 1-bits in a single number. Hamming distance between two numbers is the count of positions where their bits differ โ€” which is just the Hamming weight of their XOR. So "Hamming distance of a and b" = popcount(a ^ b). "Total Hamming distance of all pairs" (LeetCode 477) counts, at each bit position, how many numbers have that bit set (c) vs unset (n-c). Each position contributes c ร— (n-c) to the total.

Tricks to Recognize

  • "O(1) space, find missing/duplicate" โ†’ XOR or bit counting
  • "Power of 2, 4, or 2^k" โ†’ n & (n-1) plus extra checks
  • "Reverse bits" โ†’ iterative bit-by-bit or divide-and-conquer swaps
  • "Hamming distance" โ†’ XOR the two numbers, then count set bits
  • "All subsets of size k" โ†’ Gosper's hack to enumerate k-bit combinations efficiently
  • "Add without +/โˆ’" โ†’ XOR for sum without carry, AND then left-shift for carry, repeat
  • "Multiply/divide by power of 2" โ†’ left/right shift

Practice Problems

  • 136. Single Number โ€” Easy โ€” XOR everything. The purest bit manipulation problem. If you can't solve this, review XOR properties.
  • 191. Number of 1 Bits โ€” Easy โ€” Count set bits. Practice Brian Kernighan's n & (n-1) trick.
  • 338. Counting Bits โ€” Easy โ€” Count set bits for every number from 0 to n. DP using: bits(n) = bits(n & (n-1)) + 1.
  • 137. Single Number II โ€” Medium โ€” Every element appears 3 times except one. Count bits modulo 3 at each position.
  • 260. Single Number III โ€” Medium โ€” Two unique numbers. XOR + partition by differentiating bit.
  • 190. Reverse Bits โ€” Easy โ€” Reverse a 32-bit integer. Iterative or divide-and-conquer approaches.
  • 477. Total Hamming Distance โ€” Medium โ€” Sum of Hamming distances between all pairs. Count bits at each position: count ร— (n โˆ’ count).
  • 421. Maximum XOR of Two Numbers โ€” Medium โ€” Trie-based or greedy bit-by-bit approach. Builds a binary trie for O(n) solution.