Bit Manipulation: Power of Bits

Master bitwise operations for flag handling, number tricks, bit counting, and interview problems involving O(1) space arithmetic.

published: reading time: 16 min read author: GeekWorkBench

Bit Manipulation: Power of Bits

Every integer hides a secret in its binary representation. Bit manipulation lets you exploit these hidden patterns to solve problems in O(1) space, perform arithmetic that seems to require division or multiplication, and encode information compactly. Interviewers love bit problems because they test whether you understand how computers actually work under the hood—and there are only a handful of classic patterns to master.

The six fundamental operations—AND, OR, XOR, NOT, left shift, and right shift—combine in surprising ways. XOR is particularly magical: a^a = 0 and a^0 = a, which makes it perfect for finding unique elements or swapping variables without temp storage. Left shift multiplies by 2; right shift divides by 2 (floored). Combining these primitives can replace expensive operations entirely.

Introduction

Every integer hides a secret in its binary representation. Bit manipulation lets you exploit these hidden patterns to solve problems in O(1) space, perform arithmetic that seems to require division or multiplication, and encode information compactly. Interviewers love bit problems because they test whether you understand how computers actually work under the hood—and there are only a handful of classic patterns to master.

The six fundamental operations—AND, OR, XOR, NOT, left shift, and right shift—combine in surprising ways. XOR is particularly magical: a^a = 0 and a^0 = a, which makes it perfect for finding unique elements or swapping variables without temp storage. Left shift multiplies by 2; right shift divides by 2 (floored). Combining these primitives can replace expensive operations entirely.

When to Use

Bit manipulation shines when:

  • Space is at a premium — Flags, bitmaps, and compact encodings save memory
  • Arithmetic constraints — “No multiplication/division” problems use shifts instead
  • Finding unique element — XOR identifies the element appearing once among pairs
  • Bit counting — Hamming weight problems for parity or similarity measures
  • Power of 2 checks — Single bit means exactly one set bit

When Not to Use

  • When readability matters more than optimization (bit hacks can obscure intent)
  • Languages without unsigned integers or with platform-dependent shift behavior
  • When standard library functions already solve the problem clearly

Core Operations Reference

OperationSymbolTruth TableCommon Use
AND&1&1=1, else 0Clear bits, isolate masks
OR|0|0=0, else 1Set bits
XOR^1^1=0, 0^0=0, else 1Toggle, find unique
NOT~Flips all bitsTwo’s complement math
Left Shift<<x << n = x × 2ⁿMultiply by power of 2
Right Shift>>x >> n = x ÷ 2ⁿ (floor)Divide by power of 2

Production Failure Scenarios

FailureCauseMitigation
Signed vs unsigned overflowRight shift of negative numbers is implementation-definedUse unsigned types or mask with 0x7FFFFFFF
Off-by-one in bit isolationConfusing 1-indexed vs 0-indexed bit positionsDraw binary representation explicitly
NOT vs negative numbers~x = -x - 1 in two’s complement catches everyone off guardAlways test with edge cases like 0, 1, -1
JavaScript bit operationsJavaScript treats all numbers as 64-bit float, then convertsUse >>> 0 to force unsigned 32-bit

Trade-off Table

ApproachOperation CountReadabilityPerformance
Arithmetic1HighSlower on older hardware
Bit shift1MediumFaster on all hardware
Lookup table1HighFastest but uses memory
Modular arithmeticMultipleMediumVariable

For most modern code, readability wins. If you’re writing crypto or low-level systems code, the bit tricks matter more.

Implementation

Bitwise Operations Reference

Reference: Bitwise Operations Reference

Swap Two Numbers Without Temp

def swap(a, b):
    """
    Swap using XOR (only works for distinct values).
    """
    a = a ^ b
    b = a ^ b  # b = (a ^ b) ^ b = a
    a = a ^ b  # a = (a ^ b) ^ a = b
    return a, b

Check if Number is Power of 2

def is_power_of_two(n):
    """
    Power of 2 has exactly one set bit.
    n & (n-1) clears the lowest set bit.
    If result is 0, exactly one bit was set.
    """
    return n > 0 and (n & (n - 1)) == 0

Count Set Bits (Hamming Weight)

def count_bits(n):
    """
    Count set bits using Brian-Kernighan algorithm.
    """
    count = 0
    while n:
        n &= (n - 1)  # Clear lowest set bit
        count += 1
    return count

Find Single Element in Array of Pairs

def find_single(nums):
    """
    XOR all elements. Pairs cancel out to 0, singles remain.
    """
    result = 0
    for num in nums:
        result ^= num
    return result

Bitwise AND Range [m, n]

def range_bitwise_and(m, n):
    """
    Find AND of all numbers from m to n.
    The answer is the common prefix of m and n.
    """
    shift = 0
    while m != n:
        m >>= 1
        n >>= 1
        shift += 1
    return m << shift

Power of Two Check Alternative

def is_power_of_two_v2(n):
    """
    Using n & (-n) isolates the lowest set bit.
    Power of 2 has only one set bit, so n & (-n) == n.
    """
    return n > 0 and (n & (-n)) == n

Common Pitfalls / Anti-Patterns

  1. Confusing XOR with addition — XOR adds without carry; 1 ^ 1 = 0, not 2. This trips people up every time.
  2. Off-by-one in bit ranges — The bit at position 0 is the least significant (2^0=1). Always draw it out.
  3. Right shift of negatives — Result is implementation-defined in C; use masks for portability across compilers.
  4. JavaScript quirks — Bitwise operations convert to 32-bit signed integers first, then convert back. Use >>> 0 to force unsigned.
  5. Integer overflow — Shifting left past bit width is undefined behavior in C. Your compiler won’t always warn you.

Quick Recap Checklist

  • XOR properties: a^a=0, a^0=a, a^b=b^a
  • n & (n-1) clears lowest set bit
  • n & (-n) isolates lowest set bit
  • Left shift multiplies, right shift divides (floor for negatives)
  • Two’s complement: ~x = -x - 1

Interview Questions

1. How does n & (n-1) clear the lowest set bit?

In binary, n-1 flips all bits from the lowest set bit downward. For n = 1000 (8), n-1 = 0111 (7). The AND operation clears the lowest set bit: 1000 & 0111 = 0000. This property makes counting set bits and checking power-of-two very efficient.

2. Why is XOR used for swapping without a temporary variable?

XOR's properties enable self-inverse swapping: a ^ a ^ b = b. Step 1: a = a ^ b (a now holds a^b) Step 2: b = a ^ b = (a ^ b) ^ b = a (b now holds original a) Step 3: a = a ^ b = (a ^ b) ^ a = b (a now holds original b) This fails if a and b point to the same memory location—always check for that.

3. How do you find the two numbers that appear once while all others appear twice?

XOR all elements to get x ^ y where x and y are the unique numbers. Find any set bit in this result (e.g., x ^ y & -(x ^ y)). Partition numbers by whether that bit is set, XOR each partition separately. The partitions separate x and y because they differ on that bit, while pairs cancel within their partition.

4. What's the difference between left shift and right shift?

Left shift (<<) adds zeros on the right, effectively multiplying by 2ⁿ. Right shift (>>) discards the rightmost bits, floored division by 2ⁿ. For negative numbers, right shift typically sign-extends (arithmetic shift) rather than zero-filling (logical shift), which is implementation-defined behavior.

5. How do you check if a number is a power of 4?

A power of 4 has exactly one set bit located at an even position (0-indexed). Check n > 0 && (n & (n - 1)) == 0 (power of 2) AND (n & 0xAAAAAAAAAAAAAAAA) == 0 (no set bits at odd positions). Alternatively, check n % 3 == 1 since powers of 4 modulo 3 always equal 1.

6. Explain the SWAR (SIMD Within a Register) technique for popcount.

SWAR computes popcount in O(log n) without branches by treating the register as packed SIMD lanes. Steps:

  • Pairwise sum adjacent bits using mask 0x55555555
  • Sum adjacent pairs with mask 0x33333333
  • Sum adjacent nibbles with mask 0x0F0F0F0F
  • Multiply by 0x01010101 or continue summing bytes/words
7. How would you add two integers without using the + or - operators?

Use bitwise XOR and AND with left shift in a loop:

  • XOR (^) computes the sum without carry
  • AND (&) << 1 computes the carry bits shifted left
  • Repeat until carry is 0

This is how ALUs implement addition at the hardware level.

8. How can you compute the absolute value of an integer without branching?

Compute the sign bit as mask = x >> 31 (or x >>> 31 in JS). Then abs = (x + mask) ^ mask. This equals ~x + 1 (negation) when x is negative and identity when x is positive. This avoids pipeline-flushing conditional branches.

9. How do you check if two integers have opposite signs without using comparison operators?

XOR the sign bits: (a ^ b) < 0. If the result is negative, their sign bits differ (most significant bit is 1). This is a common trick in sorting networks and branchless comparisons where you need to avoid branches.

10. How do you find the position of the only set bit in a number?

Use log2(n & (-n)) to isolate the lowest set bit and compute its 0-indexed position. In C/C++:

  • ffs() — find first set (1-indexed)
  • __builtin_ctz() — count trailing zeros

Always verify the number is a power of 2 first if you expect only one set bit.

11. What is the XOR of all numbers from 1 to N? Derive the pattern.

The XOR from 1 to N follows a repeating 4-cycle pattern:

  • N % 4 == 0 → N
  • N % 4 == 1 → 1
  • N % 4 == 2 → N + 1
  • N % 4 == 3 → 0

This works because XOR is associative and pairs (i, i+1) cancel in predictable ways. It's used to find the missing number by XORing expected vs actual values.

12. How does two's complement work and why is ~x = -x - 1?

In two's complement, negative numbers are represented as the bitwise NOT plus 1: -x = ~x + 1. Rearranging gives ~x = -x - 1.

  • Adding a number and its two's complement yields zero (with overflow)
  • The MSB acts as a sign bit: 0 for positive, 1 for negative
  • Range is asymmetric: -2^(n-1) to 2^(n-1) - 1
13. How can you use bitmasks to represent and query sets of flags efficiently?

Assign each flag a distinct power of 2 (bit position). Combine with OR, test with AND:

  • Set flag: flags |= mask
  • Clear flag: flags &= ~mask
  • Toggle: flags ^= mask
  • Test: (flags & mask) != 0

This compresses up to 64 boolean flags into a single 64-bit integer, enabling constant-time operations and SIMD-friendly data structures.

14. How do you reverse the bits of an integer?

Use a divide-and-conquer swap approach (similar to SWAR):

  • Swap adjacent bits with mask 0x55555555
  • Swap adjacent bit pairs with mask 0x33333333
  • Swap adjacent nibbles with mask 0x0F0F0F0F
  • Swap bytes (16-bit chunks)

This runs in O(log n) operations. For 32-bit integers, this takes 5 steps. Alternatively, use a lookup table mapping bytes to their reversed form.

15. How do you find the missing number in an array of 0..n?

XOR all array elements with all numbers from 0 to n. Pairs cancel out (a^a=0), leaving only the missing number. This works in O(n) time and O(1) space without modifying the array, and avoids overflow that would occur with summation.

16. How can you check if a number has alternating bits?

XOR the number with itself shifted right by 1: (n ^ (n >> 1)). If the number has alternating bits, the result will be all 1s (like 0b111...). Then check if that result + 1 is a power of 2: (((n ^ (n >> 1)) + 1) & (n ^ (n >> 1))) == 0.

17. How does the Gray code work and how do you generate it?

A Gray code sequence has consecutive values differing by exactly one bit. The n-th Gray code is n ^ (n >> 1). Binary to Gray and Gray to binary conversion:

  • Binary → Gray: gray = bin ^ (bin >> 1)
  • Gray → Binary: bin = gray; while (gray >>= 1) bin ^= gray

Applications: Karnaugh maps, error correction, analog-to-digital converters.

18. What is the difference between arithmetic right shift and logical right shift?

Arithmetic right shift (>>) preserves the sign bit by filling vacated bits with the MSB (sign-extension). Logical right shift (>>>) fills vacated bits with zeros regardless of sign. In most languages, >> is arithmetic for signed types and logical for unsigned types. JavaScript needs >>> 0 to force a 32-bit unsigned conversion before shifting.

19. How do you count the number of bits to flip to convert integer A to integer B?

XOR A and B, then count the set bits in the result: popcount(A ^ B). Each set bit in the XOR indicates a position where A and B differ, requiring a flip. This is the Hamming distance between two integers and is used in error detection, cryptography, and similarity measures.

20. How do you determine if a number is a multiple of 2 without using the modulo operator?

Check the least significant bit: (n & 1) == 0. In binary, even numbers always end with 0 and odd numbers with 1. This is faster than modulo on some architectures and is the canonical way to check parity in performance-sensitive code.

Further Reading

Books & Articles

  • Hacker’s Delight (Henry S. Warren Jr.) — The definitive reference on bit manipulation tricks, covering everything from population count to cyclic redundancy checks.
  • Matters Computational (Jörg Arndt) — Comprehensive coverage of bitwise algorithms including Gray codes, bit permutations, and low-level number-theoretic transforms.
  • Bit Twiddling Hacks (Sean Eron Anderson) — A curated collection of battle-tested bit manipulation snippets collected from decades of real-world use.

Topic-Specific Deep Dives

Subset Enumeration Using Bitmasks

Represent any subset of an N-element set as an N-bit integer where the i-th bit is 1 if element i is included. Iterating from 0 to 2^N - 1 enumerates all subsets in Gray-code order when using s = s & (s - 1) to visit only set bits. This is the foundation of DP over subsets (e.g., traveling salesman problem with bitmask DP).

# Enumerate all subsets of a set represented by bitmask
mask = 0b1101  # elements 0, 2, 3
sub = mask
while sub:
    # process subset 'sub'
    sub = (sub - 1) & mask

Popcount (Hamming Weight) Variations

Beyond Brian Kernighan’s algorithm, hardware intrinsics like POPCNT on x86 and __builtin_popcount() in GCC offer O(1) popcount. For software implementations, the parallel-prefix divide-and-conquer method (also called “SWAR” — SIMD within a register) runs in O(log n) without loops:

def popcount_swar(x):
    x = x - ((x >> 1) & 0x55555555)
    x = (x & 0x33333333) + ((x >> 2) & 0x33333333)
    x = (x + (x >> 4)) & 0x0F0F0F0F
    x = x + (x >> 8)
    x = x + (x >> 16)
    return x & 0x3F

Gray Code Generation

Gray codes ensure consecutive values differ by exactly one bit, useful for Karnaugh maps, error correction, and analog-to-digital converters. The n-th Gray code is computed as n ^ (n >> 1).

Sign Extension and Endianness

When extracting bitfields from network protocols or binary file formats, sign extension (arithmetic right shift vs. logical) and endianness (byte ordering) become critical. Always mask before shifting to avoid platform-dependent behavior.

Online Resources

Conclusion

Bit manipulation lets you exploit patterns in binary representations to solve problems in O(1) space or replace expensive operations with cheap shifts. XOR is particularly useful: a^a = 0 and a^0 = a, which finds unique elements among pairs and swaps variables without temp storage. The n & (n-1) trick clears the lowest set bit—counting bits or checking power-of-two both use this. Left shift multiplies by powers of 2, right shift divides (floored). Watch out for signed vs. unsigned overflow on right shifts, and remember that JavaScript converts to 32-bit signed integers before bitwise operations (use >>> 0 for unsigned). When readability matters more than micro-optimization, use standard library functions instead—bit hacks can obscure intent.

Category

Related Posts

Binary Search Variants: Beyond Simple Lookup

Master variations of binary search including lower bound, upper bound, search in rotated array, and fractional searching for optimization problems.

#binary-search #algorithms #searching

Common Coding Interview Patterns

Master the essential patterns—sliding window, two pointers, fast-slow pointers—that solve 80% of linked list and array problems.

#coding-interview #problem-solving #patterns

Sliding Window: Dynamic Subarray Problems

Solve maximum subarray, longest substring, and subarray average problems with O(n) time using the sliding window technique.

#sliding-window #algorithms #data-structures