In short
Bit manipulation is the use of the bitwise operators (AND, OR, XOR, NOT, shifts) to read, set and transform the individual binary digits of an integer, treating the integer as a fixed-width array of flags rather than as a quantity. It matters because a single machine instruction does work that would otherwise need a loop or an array: a 32-bit word becomes a set of up to 32 elements, and subset problems become counting from 0 to 2n − 1. It is examined in GATE (through two's complement arithmetic in Digital Logic and through C programming questions), in university Data Structures and Computer Organisation papers, and in almost every placement coding round — Amazon, Microsoft, Google India, Flipkart, Zoho, Goldman Sachs and Morgan Stanley all ask XOR and bitmask questions. On the job it is unavoidable in embedded and systems work (Qualcomm India, Bosch, Texas Instruments India, Tata Elxsi, ISRO), in networking (subnet masks) and in competitive programming.
Explained
Binary representation and two's complement
- An
intis a fixed-width word: 32 bits in C/C++ (typical) and 32 bits in Java; 64 bits forlong longin C/C++ and forlongin Java (C/C++longis 32-bit on Windows, 64-bit on Linux). Python integers are arbitrary precision and have no fixed width. - Bits are numbered from 0 (least significant, value 20) upward. Bit *i* of *x* has value 2i if set.
- Two's complement stores negatives as −x = ~x + 1, so one adder serves both addition and subtraction and there is a single zero. An n-bit signed range is −2n−1 to 2n−1 − 1; for 32 bits, −2,147,483,648 to 2,147,483,647.
- The most significant bit is the sign bit:
x < 0is equivalent to((x >> 31) & 1) == 1for a 32-bitint. - Consequence used constantly:
-xleaves the lowest set bit of *x* and all the zeros below it unchanged, and inverts everything above it — which is whyx & -xisolates the lowest set bit. INT_MINis its own negation (overflow), soabs(INT_MIN)is undefined behaviour in C/C++ andMath.abs(Integer.MIN_VALUE)returns a negative number in Java.
The operators and the four basic bit operations
| Operator | Name | Rule | Typical use | |
|---|---|---|---|---|
& | AND | 1 only if both are 1 | masking, testing, clearing | |
| `\ | ` | OR | 1 if either is 1 | setting bits, union |
^ | XOR | 1 if the bits differ | toggling, cancelling pairs | |
~ | NOT | flips every bit | building masks | |
<< | left shift | x << k = x × 2k | multiply, build 1<<i | |
>> | right shift | arithmetic (sign-filling) for signed types | divide by 2k, extract fields | |
>>> | unsigned right shift (Java only) | fills with 0 | treating an int as unsigned |
- Test bit i:
(x >> i) & 1, or(x & (1 << i)) != 0. - Set bit i:
x |= (1 << i). Clear bit i:x &= ~(1 << i). Toggle bit i:x ^= (1 << i). - Extract the low k bits:
x & ((1 << k) - 1). Extract a field of width w starting at bit p:(x >> p) & ((1 << w) - 1). x << kequals x × 2k only while the result fits; overflow of a signed left shift is undefined behaviour in C/C++.x >> kequals ⌊x / 2k⌋ — note this *floors*, so-21 >> 2is −6 while-21 / 4is −5 in C and Java.- Shifting by a count ≥ the type width is undefined behaviour in C/C++; in Java the count is taken modulo 32 (or 64 for
long), so1 << 32evaluates to 1.
XOR: the properties that generate the question family
- Identities:
x ^ 0 = x,x ^ x = 0, XOR is commutative and associative. Therefore XOR of a list cancels every value that appears an even number of times. - Single number (every element twice except one): XOR the whole array; the survivor is the answer. O(n) time, O(1) space — the reason it is asked instead of hashing.
- Missing number in 0…n: XOR all indices and all values; everything pairs off except the missing one.
- Two single numbers (all others twice): XOR everything to get
d = a ^ b; takebit = d & -d(a bit where *a* and *b* differ); partition the array by that bit and XOR each half separately. - Every element thrice except one: maintain two accumulators,
ones = (ones ^ x) & ~twos; twos = (twos ^ x) & ~ones;— a 2-bit per-position counter mod 3. The general form counts mod k using ⌈log2 k⌉ accumulators. - Swap without a temporary:
a ^= b; b ^= a; a ^= b;— correct only ifaandbare different objects; if they alias, both become 0. - Prefix XOR:
pre[i] = a[0]^…^a[i-1], so the XOR of a subarray (l, r) ispre[r+1] ^ pre[l]— the XOR analogue of prefix sums, used for "count subarrays with XOR = k" with a hash map. - Parity of a word = XOR of all its bits =
popcount(x) & 1; computed by foldingx ^= x >> 16; x ^= x >> 8; …. - Gray code:
g = n ^ (n >> 1)— consecutive codes differ in exactly one bit, used in rotary encoders and Karnaugh maps.
Counting and locating bits
- Brian Kernighan's algorithm:
while (x) { x &= x - 1; count++; }runs once per *set* bit, so it is O(popcount) rather than O(width). - Why
x & (x-1)works: subtracting 1 flips the lowest set bit to 0 and all zeros below it to 1; ANDing keeps the higher bits unchanged and wipes that block. - Built-ins (single instruction on modern CPUs): GCC/Clang
__builtin_popcount,__builtin_popcountll,__builtin_ctz(trailing zeros = index of lowest set bit),__builtin_clz(leading zeros); C++20std::popcount,std::countr_zero,std::bit_widthin<bit>; JavaInteger.bitCount,Integer.numberOfTrailingZeros,Integer.highestOneBit,Integer.reverse; Pythonbin(x).count('1'), orint.bit_count()from Python 3.10 andx.bit_length(). __builtin_clz(0)and__builtin_ctz(0)are undefined — guard for zero.- Counting set bits for all numbers 0…n (DP):
dp[i] = dp[i >> 1] + (i & 1)ordp[i] = dp[i & (i-1)] + 1, both O(n). - Useful idioms:
x & -xisolates the lowest set bit;x | (x+1)sets the lowest zero bit;x & (x+1)clears all trailing ones;~x & (x+1)isolates the lowest zero bit;x | (x-1)sets all bits below the lowest set bit. - Power of two:
x > 0 && (x & (x-1)) == 0. Next power of two ≥ x:1 << (32 - __builtin_clz(x-1))for x > 1. - Branchless tricks:
abs(x)asm = x >> 31; (x ^ m) - m; opposite signs as(x ^ y) < 0;min(a,b)asb ^ ((a ^ b) & -(a < b)). - **Addition without
+:**while (b) { carry = a & b; a = a ^ b; b = carry << 1; }— XOR is the sum bit, AND is the carry.
Bitmasks: sets, subsets and bitmask DP
- A mask of n bits represents a subset of an n-element universe: bit *i* set means element *i* is in the set. Natural for n up to about 20–25.
- Set operations become single instructions: union
a | b, intersectiona & b, differencea & ~b, symmetric differencea ^ b, membership(a >> i) & 1, sizepopcount(a), complement((1 << n) - 1) ^ a. - Enumerate all subsets:
for (int m = 0; m < (1 << n); m++)— 2n masks. - Enumerate all submasks of m:
for (int s = m; s; s = (s-1) & m)then handles = 0separately. Over all masks this costs 3n in total, not 4n — each element is in the submask, in m-but-not-submask, or outside. - Bitmask DP stores a state as (set of items already used, extra info). Standard problems: travelling salesman
dp[mask][i]in O(2n · n²) time and O(2n · n) space; assignment of n jobs to n workers in O(2n · n); counting Hamiltonian paths; "minimum number of groups" partition DP in O(3n). - Sum over subsets (SOS DP): for each bit,
for (m) if (m >> i & 1) f[m] += f[m ^ (1 << i)];gives, in O(n · 2n), the aggregate over every submask. - Gosper's hack — next integer with the same popcount:
c = x & -x; r = x + c; next = (((r ^ x) >> 2) / c) | r;— used to enumerate all k-subsets in increasing order. - **
std::bitset<N>** packs N bits into words, supports&,|,^,count(),_Find_first(), and gives roughly a 64× constant-factor speedup and an 8× memory saving overbool[].
Language-specific behaviour and pitfalls
| Issue | C/C++ | Java | Python |
|---|---|---|---|
| Integer width | fixed, 32/64-bit, overflow UB for signed | fixed 32/64-bit, wraps silently | arbitrary precision |
| Right shift of a negative | arithmetic; guaranteed two's complement from C++20 | >> arithmetic, >>> logical | >> arithmetic (floors) |
| Shift count ≥ width | undefined behaviour | taken modulo 32 / 64 | allowed, gives 0 (or −1) |
~x | bitwise NOT of fixed width | same | ~x = -x - 1, conceptually infinite sign bits |
| Simulating 32-bit | native | native | mask with & 0xFFFFFFFF, then subtract 232 if bit 31 set |
| Popcount | __builtin_popcount / std::popcount | Integer.bitCount | x.bit_count() (3.10+) |
- Operator precedence is the single most common bug:
&,|,^bind *looser* than==and!=in C, C++ and Java, soif (x & 1 == 0)is parsed asif (x & (1 == 0)). Always bracket:if ((x & 1) == 0). - Use
1LL << k(C/C++) or1L << k(Java) when k ≥ 31;1 << 31on a 32-bitintoverflows into the sign bit. - Unsigned types wrap predictably and are the safe choice for pure bit work; mixing signed and unsigned in a comparison converts the signed value to unsigned and can invert the result.
Advantages, limits and complexity
- Advantages: O(1) set operations on up to 64 elements; constant memory for problems that otherwise need a hash set; cache-friendly dense storage; avoids branches, which helps the pipeline.
- Limits: masks are practical only to about n = 25 (225 ≈ 33.5 million states); the code is hard to read and easy to get wrong; micro-tricks rarely beat a modern optimising compiler on ordinary arithmetic; behaviour differs across languages and widths, so bit code ports badly.
- Complexity note: bit tricks change the *constant factor*, not usually the asymptotic class — except in bitmask DP, where they convert a factorial search into an exponential one (14! ≈ 8.7 × 1010 versus 215 · 15² ≈ 7.4 × 106).
Key points
- Two's complement - −x = ~x + 1; the sign bit is the MSB; the n-bit signed range is −2n−1 … 2n−1 − 1.
- Test / set / clear / toggle -
(x>>i)&1,x|(1<<i),x&~(1<<i),x^(1<<i). - **
x & (x-1)** - clears the lowest set bit; zero result (with x > 0) means a power of two. - **
x & -x** - isolates the lowest set bit; the basis of Fenwick trees and of splitting on a differing bit. - XOR cancels pairs -
x^x = 0,x^0 = x; the whole "single number / missing number" family follows. - Brian Kernighan - popcount in O(number of set bits), not O(width).
- Built-in popcount/ctz/clz - map to one CPU instruction;
ctz(0)andclz(0)are undefined. - Shift as arithmetic -
x<<k= x·2k,x>>k= ⌊x/2k⌋ (floors, so it differs from/for negatives). - Mask of k ones -
(1<<k) - 1; use1LL<<kfor k ≥ 31. - Subsets of n elements - 2n masks; iterate
m = 0 … (1<<n)-1. - Submask loop -
for (s = m; s; s = (s-1) & m); total cost over all masks is 3n. - Bitmask DP - TSP and assignment in O(2n · n²) / O(2n · n), viable to n ≈ 20.
- Gray code -
n ^ (n>>1); successive values differ in one bit. - Hamming distance -
popcount(x ^ y). - Precedence trap -
&,^,|are looser than==; always parenthesise. - **Java
>>vs>>>** - arithmetic versus logical shift; Python has no>>>, mask with0xFFFFFFFFinstead. - **
bitset<N>** - 8× less memory thanbool[]and a ~64× constant-factor speedup on set operations. - XOR 1…n has period 4 - n, 1, n+1, 0 for n mod 4 = 0, 1, 2, 3.
Formulas and examples
Test: (x >> i) & 1 · Set: x | (1 << i) · Clear: x & ~(1 << i) · Toggle: x ^ (1 << i)
Low k bits: x & ((1 << k) − 1) · Field of width w at position p: (x >> p) & ((1 << w) − 1)
x << k = x · 2k (if no overflow); x >> k = ⌊x / 2k⌋ (arithmetic shift, floors)
x & (x − 1) = x with its lowest set bit cleared; x & (−x) = value of the lowest set bit
Power of two: (x > 0) AND ((x & (x − 1)) == 0)
Gray(n) = n ^ (n >> 1); inverse: x = g; for (s = 16; s; s >>= 1) x ^= x >> s
Hamming distance d(x, y) = popcount(x ^ y) bits
XOR of 1…n = n if n mod 4 = 0; 1 if 1; n + 1 if 2; 0 if 3
Number of subsets of an n-set = 2n; number of (mask, submask) pairs = 3n
Bitmask TSP: dp[mask][i] = min over j in mask of dp[mask ^ (1<<i)][j] + cost[j][i]; O(2n · n²) time, O(2n · n) space
Example 1 — bit surgery and two's complement. Let x = 183 = 1011 0111.
- Bits set: 7, 5, 4, 2, 1, 0 → 128 + 32 + 16 + 4 + 2 + 1 = 183. ✔
- Test bit 3:
(183 >> 3) & 1=10110& 1 = 22 & 1 = 0 (clear). - Set bit 3: 183 | 8 = 191. Clear bit 5: 183 & ~32 = 183 − 32 = 151. Toggle bit 0: 183 ^ 1 = 182.
- High nibble:
(183 >> 4) & 0xF= 11 & 15 = 11 (0xB). Low nibble: 183 & 15 = 7. - Two's complement of −20 in 32 bits: 20 =
…0001 0100; invert →…1110 1011; +1 →…1110 1100= 0xFFFFFFEC (= 232 − 20 = 4,294,967,276 read as unsigned). -21 >> 2= −6 (floor of −5.25) whereas-21 / 4= −5 in C and Java — a classic off-by-one when a shift replaces a division.
Example 2 — popcount by Brian Kernighan, and Hamming distance. x = 156 = 1001 1100.
| Step | x (binary) | x − 1 | x & (x−1) | count |
|---|---|---|---|---|
| 1 | 10011100 (156) | 10011011 (155) | 10011000 (152) | 1 |
| 2 | 10011000 (152) | 10010111 (151) | 10010000 (144) | 2 |
| 3 | 10010000 (144) | 10001111 (143) | 10000000 (128) | 3 |
| 4 | 10000000 (128) | 01111111 (127) | 00000000 (0) | 4 |
- popcount(156) = 4 in 4 iterations, not 8.
156 & -156= 4 (lowest set bit, bit 2). 156 is not a power of two since156 & 155= 152 ≠ 0; 1024 & 1023 = 0, so 1024 is. - Hamming distance between 29 (
11101) and 23 (10111): XOR =01010= 10, popcount = 2 bits.
Example 3 — the two single numbers. Array [2, 4, 7, 9, 2, 4]; every value appears twice except 7 and 9.
- XOR all: 24 = 6; 67 = 1; 19 = 8; 82 = 10; 104 = 14. So d = 14 =
1110= 7 ^ 9. ✔ - Lowest set bit:
14 & -14= 2 (bit 1). - Partition on bit 1 — set: {2 (
0010), 7 (0111), 2}; clear: {4 (0100), 9 (1001), 4}. - XOR each group: 272 = 7; 494 = 9. Answer {7, 9}, in O(n) time and O(1) space.
- Cross-check with the period-4 rule: XOR of 1…10, with 10 mod 4 = 2, should be 11. Longhand: 12 = 3, 3 = 0, 4 = 4, 5 = 1, 6 = 7, 7 = 0, 8 = 8, 9 = 1, 10 = 11. ✔
Example 4 — subset enumeration with a mask. Set {5, 9, 12}, n = 3, so 23 = 8 masks.
| mask | bits | subset | sum |
|---|---|---|---|
| 0 | 000 | { } | 0 |
| 1 | 001 | {5} | 5 |
| 2 | 010 | {9} | 9 |
| 3 | 011 | {5, 9} | 14 |
| 4 | 100 | {12} | 12 |
| 5 | 101 | {5, 12} | 17 |
| 6 | 110 | {9, 12} | 21 |
| 7 | 111 | {5, 9, 12} | 26 |
- Subsets with sum ≥ 20: masks 6 and 7 → 2 subsets. Submasks of mask 6 (
110): s = 6, then (6−1)&6 = 4, then (4−1)&6 = 2, then (2−1)&6 = 0 → stop → {6, 4, 2} plus the empty mask = 4 submasks = 2popcount(6) = 2² = 4. ✔
Example 5 — why bitmask DP is worth the ugliness (TSP, n cities).
| n | Brute force (n−1)! | Bitmask DP 2n · n² | States 2n · n |
|---|---|---|---|
| 10 | 362,880 | 102,400 | 10,240 |
| 15 | 87,178,291,200 | 7,372,800 | 491,520 |
| 20 | 1.22 × 1017 | 419,430,400 | 20,971,520 |
- At n = 15 the DP is about 11,800× fewer operations; at n = 20 it is still feasible (≈4 × 108 transitions, ~21 M states), while brute force is not.
- Memory parallel: marking 108 numbers with
bool[]costs 100 MB; abitsetcosts 108 / 8 = 12.5 MB — the reason large sieves use bitsets.
// idiom sheet
bool isPow2(unsigned x) { return x && !(x & (x - 1)); }
int lowbit(int x) { return x & -x; }
int popcnt(int x) { int c = 0; while (x) { x &= x - 1; ++c; } return c; }
int gray(int n) { return n ^ (n >> 1); }
// all submasks of m, including 0
for (int s = m; ; s = (s - 1) & m) { use(s); if (!s) break; }
Where it is used
- Embedded and VLSI firms (Bosch, Texas Instruments India, Qualcomm India, Tata Elxsi, L&T Technology Services, ISRO/DRDO labs): writing and reading microcontroller registers —
GPIOA->ODR |= (1 << 5)to set a pin,&= ~to clear — and packing sensor fields into CAN frames. - Networking and cloud infrastructure: IPv4 subnet masks and CIDR (
/24is0xFFFFFF00), computing a network address asip & maskand broadcast asip | ~mask; flags in TCP headers; used daily by network and SRE roles at Jio, Airtel, Cisco India and TCS/Infosys infra teams. - Databases, search and analytics: roaring bitmaps and bitmap indexes for filtering, Bloom filters for membership tests, compressed inverted indexes at Flipkart, Swiggy, Zomato and Sprinklr-type product teams.
- Permissions and feature flags: Unix
chmodoctal modes are three 3-bit fields; application role masks and entitlement bits in banking backends (Goldman Sachs, Morgan Stanley, JPMorgan Chase technology centres in India). - Competitive programming and hiring tests: bitmask DP and XOR problems on Codeforces, CodeChef and ICPC, and in the DSA rounds of Amazon, Microsoft, Google, Adobe, Zoho, Directi/Media.net and Arcesium.
- Graphics, compression and cryptography: colour channel packing (
0xRRGGBB), Huffman and arithmetic coding bit streams, parity and CRC in storage, and the XOR/rotate/shift core of block ciphers and hash functions.
Interview questions
- Q: How do you check whether a number is a power of two in O(1)? A:
x > 0 && (x & (x - 1)) == 0— a power of two has exactly one set bit, and subtracting 1 flips that bit and sets all lower bits, so the AND is zero. Thex > 0guard excludes 0 and negatives. - Q: Why does
x & -xgive the lowest set bit? A: In two's complement,-x = ~x + 1, which leaves the bits below and including the lowest set bit unchanged in value and inverts everything above it, so only that one bit survives the AND. - Q: An array has every element twice except one. Find it in O(1) space. A: XOR all elements; equal values cancel because
a ^ a = 0, leaving the unique element. - Q: Extend that to exactly two unique elements. A: XOR everything to get
d = a ^ b, take any set bit of d (used & -d), split the array by that bit and XOR each half — a and b must fall into different halves. - Q: Count set bits faster than looping over 32 positions. A: Brian Kernighan's
x &= x - 1loop runs once per set bit, or use a single-instruction built-in such as__builtin_popcount/Integer.bitCount/std::popcount. - Q: What is the difference between
>>and>>>in Java? A:>>is arithmetic and copies the sign bit, so-8 >> 1is −4;>>>is logical and fills with zeros, so-8 >>> 1is 2,147,483,644. C and C++ have no>>>; use an unsigned type. - Q: Is
x >> 1always the same asx / 2? A: No. The shift floors, division truncates towards zero, so forx = -7the shift gives −4 and the division gives −3. They agree only for non-negative x. - Q: How do you enumerate every subset of a set of size n, and every subset of a given subset? A: Loop masks 0 to 2n − 1 for all subsets; for submasks of m use
for (s = m; s; s = (s-1) & m)plus the empty mask, which costs 3n summed over all m. - Q: Why is bitmask DP limited to about 20 elements? A: The state space is 2n (times n for a TSP-style second index), so n = 20 is already ~21 million states and n = 25 is ~840 million — memory, not time, usually fails first.
- Q: Swap two numbers without a temporary. What can go wrong? A:
a ^= b; b ^= a; a ^= b;works, but if a and b are the same memory location both become 0, and in Ca ^= b ^= a ^= b;is undefined behaviour because the object is modified twice without a sequence point. - Q: What does
x & (x + 1)do, andx | (x + 1)? A:x & (x + 1)clears all trailing 1 bits;x | (x + 1)sets the lowest 0 bit. - Q: How would you compute
a + busing only bitwise operators? A: Loop:sum = a ^ b(addition without carry),carry = (a & b) << 1, repeat with a = sum, b = carry until carry is 0. - Q: Find the single element where every other appears three times. A: Keep two bitmasks acting as a mod-3 counter per bit:
ones = (ones ^ x) & ~twos; twos = (twos ^ x) & ~ones;—onesholds the answer at the end.
Common mistakes
- Writing
if (x & 1 == 0)instead ofif ((x & 1) == 0)—==binds tighter than&, so the condition becomesx & 0, always false. - Using
1 << iwhen i can be 31 or more on a 32-bitint: it overflows into the sign bit or is undefined; use1LL << i(C/C++) or1L << i(Java). - Assuming
x >> kequalsx / 2^kfor negative x, or assuming that shifting by the word width or more gives 0 — it is undefined behaviour in C/C++ and wraps modulo 32/64 in Java. - Calling
__builtin_ctz/__builtin_clzon 0, or relying onMath.abs(Integer.MIN_VALUE)andabs(INT_MIN)— all are undefined or wrong. - Treating Python's
~xorx >> kas fixed-width: Python integers are unbounded and~5is −6, so 32-bit behaviour must be simulated with& 0xFFFFFFFFand a sign correction. - Forgetting the empty submask: the loop
for (s = m; s; s = (s-1) & m)silently skipss = 0. - Reaching for bit tricks where they do not help — micro-optimising ordinary arithmetic that the compiler already handles, at the cost of unreadable code, instead of reserving bit work for sets, flags and masks.
Related topics
Number theory for programmers · Dynamic programming · Recursion and backtracking · Time and space complexity · DSA interview patterns · Searching and binary search