Integers as arrays of bits: AND/OR/XOR/shifts, the classic tricks (clear lowest set bit, XOR to cancel pairs), bitmasks, and O(1) set operations.
0 of 6 lessons checked off
Introduction
What it is
Bit manipulation treats an integer as a fixed array of binary digits and operates on them directly with bitwise operators: AND (&), OR (|), XOR (^), NOT (~), and shifts (<<, >>).
It enables O(1) tricks — check/set/clear a bit, count set bits, use an integer as a compact set (bitmask) — that would otherwise need loops or extra structures.
Why it matters
Some problems have elegant bit-based O(n)/O(1) solutions that are near-impossible otherwise: 'find the single number among pairs' via XOR, subset enumeration via bitmasks, and state compression in DP.
It's also a systems/embedded staple, and interviewers use it to probe comfort with the machine-level view of data.
How it works
Each operator acts bit-by-bit: AND masks bits off, OR sets them, XOR flips/cancels, shifts multiply/divide by powers of two.
Key identities: x ^ x = 0 and x ^ 0 = x (XOR cancels pairs); n & (n−1) clears the lowest set bit; 1 << k is a mask for bit k.
A bitmask is an integer whose bits represent set membership: bit i set means element i is in the set — enabling O(1) union (|), intersection (&), and toggle (^).
Where it's used
Permission/feature flags (read/write/execute bits), network subnet masks, compression, hash functions, graphics color channels, and bitmask DP for the Traveling Salesman problem.
In interviews
Single number, number of 1 bits (Hamming weight), counting bits, power of two, missing number, subsets via bitmask, maximum XOR (with tries), reverse bits.
Analogy: A row of light switches, one per bit. AND keeps only switches on in both rows; OR turns on any that's on in either; XOR toggles where they differ. Counting set bits is counting lit switches — and n & (n−1) turns off the rightmost lit switch each time.
Interactive diagram
Watch each operator act on the same two numbers bit by bit.
a = 12001100
b = 10001010
a = 12, b = 10 in binary
Bitwise operators act on each bit position independently. a = 001100, b = 001010.
1 / 5
Lessons in this topic
Check off lessons as you go — your progress is saved in this browser.
Check/set/clear/toggle a bit; n & (n−1); isolate lowest set bit.
25 min
XOR patterns
Cancel pairs to find singles; missing number; swap without temp.
20 min
Counting bits
Brian Kernighan's method and the DP relation.
15 min
Bitmasks as sets
Membership, union/intersection, and subset enumeration.
25 min
Python-specific gotchas
Arbitrary-precision ints, negative numbers, masking to 32 bits.
15 min
Operations
Check, set, clear, and toggle a bit
Build a mask with 1 << k, then AND to test, OR to set, AND-NOT to clear, XOR to toggle.
Check, set, clear, and toggle a bit
1defget_bit(x:int,k:int)->int:2"""Value of bit k (0 or 1)."""3return(x>>k)&145defset_bit(x:int,k:int)->int:6"""Turn bit k on."""7returnx|(1<<k)89defclear_bit(x:int,k:int)->int:10"""Turn bit k off."""11returnx&~(1<<k)1213deftoggle_bit(x:int,k:int)->int:14"""Flip bit k."""15returnx^(1<<k)1617defis_power_of_two(n:int)->bool:18"""A power of two has exactly one set bit."""19returnn>0and(n&(n-1))==0# clears the only set bit → 0
Time: O(1)Space: O(1)
Edge cases
k must be within the integer's width you care about; Python ints are unbounded, so no overflow.
is_power_of_two must guard n > 0 (0 and negatives aren't powers of two).
n & (n−1) clears the LOWEST set bit — for a power of two that's the only bit.
Common mistakes
Confusing bitwise & with logical and (and short-circuits, & doesn't).
Forgetting the n > 0 guard, so 0 wrongly reports as a power of two.
XOR to find the unique element
XOR all numbers: every value appearing twice cancels to 0, leaving only the single unpaired value.
XOR to find the unique element
1defsingle_number(nums:list[int])->int:2"""Everyelementappearstwiceexceptone.Findit.O(n)/O(1).3Reliesonx^x=0andx^0=x,andXORbeingcommutative."""4result=05forxinnums:6result^=x# pairs cancel; order doesn't matter7returnresult8910defmissing_number(nums:list[int])->int:11"""nums holds 0..n with one missing. XOR indices against values."""12result=len(nums)# start with n13fori,xinenumerate(nums):14result^=i^x# each present value cancels its index15returnresult
Time: O(n)Space: O(1) — beats the O(n)-space hash-set approach
Edge cases
Works because XOR is commutative and associative — grouping/order is irrelevant.
single_number requires EXACTLY one unpaired element (others in pairs).
For elements appearing THREE times except one, XOR doesn't cancel — use bit-counting mod 3.
Common mistakes
Reaching for a hash map (O(n) space) when XOR is O(1) space — the intended answer.
Applying plain XOR to the 'appears 3 times' variant, where it fails.
Count set bits (Brian Kernighan)
n & (n−1) removes the lowest set bit; loop until zero, counting iterations. Runs once per set bit, not once per bit.
Count set bits (Brian Kernighan)
1defcount_set_bits(n:int)->int:2"""Number of 1 bits. O(number of set bits), not O(total bits)."""3count=04whilen:5n&=n-1# drop the lowest set bit6count+=17returncount8910defcount_bits_up_to(n:int)->list[int]:11"""count[i] = set bits in i, for 0..n. DP: O(n)."""12dp=[0]*(n+1)13foriinrange(1,n+1):14dp[i]=dp[i>>1]+(i&1)# i's bits = (i//2)'s bits + its last bit15returndp
Negative numbers in Python have infinite leading 1s conceptually — mask first if counting a fixed width.
The DP relation dp[i] = dp[i>>1] + (i&1) is the elegant 'counting bits' answer.
Common mistakes
Looping over all 32/64 bits when Kernighan's runs only per set bit.
Counting bits of a negative Python int without masking to the intended width.
Bitmask as a set (subset enumeration)
An integer's bits encode set membership; iterate 0 to 2ⁿ−1 to enumerate every subset in O(2ⁿ).
Bitmask as a set (subset enumeration)
1defall_subsets(nums:list[int])->list[list[int]]:2"""Every subset via bitmasks. O(n · 2^n)."""3n=len(nums)4result:list[list[int]]=[]5formaskinrange(1<<n):# 0 .. 2^n - 16subset=[nums[i]foriinrange(n)ifmask&(1<<i)]7result.append(subset)# bit i set ⇒ include nums[i]8returnresult91011# Bitmask set operations (all O(1)):12# add element i: mask |= (1 << i)13# remove element i: mask &= ~(1 << i)14# test membership i: mask & (1 << i)15# union / intersection: a | b / a & b16# size (popcount): bin(mask).count("1")
Time: O(n · 2ⁿ) to enumerate all subsetsSpace: O(n) per subset
Edge cases
mask = 0 is the empty subset; mask = 2ⁿ−1 is the full set.
Only practical for n ≤ ~20 (2²⁰ ≈ 1M masks).
Bitmask DP compresses a 'visited set' into one integer — the TSP/assignment technique.
Common mistakes
Using bitmask enumeration for large n (2ⁿ explodes past n≈25).
Off-by-one between bit position i and element index.
Complexity analysis
Operation
Best
Average
Worst
Space
Get/set/clear/toggle bit
O(1)
O(1)
O(1)
O(1)
XOR single/missing number
O(n)
O(n)
O(n)
O(1)
Count set bits (Kernighan)
O(1)
O(set bits)
O(bits)
O(1)
Count bits 0..n (DP)
O(n)
O(n)
O(n)
O(n)
Subset enumeration (bitmask)
O(2ⁿ)
O(n·2ⁿ)
O(n·2ⁿ)
O(n)
Bit tricks turn many O(n)-space or looping solutions into O(1)-space one-liners — the reason they're worth memorizing.
Python implementation
Production-quality code with type hints, validation, and docstrings.
1defmin_assignment_cost(cost:list[list[int]])->int:2"""nworkers,njobs;cost[w][j].Assigneachworkeronedistinctjob3atminimumtotalcost.BitmaskDPoverthesetofASSIGNEDjobs.4O(n·2^n)—feasiblefornupto~20."""5n=len(cost)6FULL=(1<<n)-17# dp[mask] = min cost to assign jobs in 'mask' to the first popcount(mask) workers8dp=[float("inf")]*(1<<n)9dp[0]=01011formaskinrange(1<<n):12ifdp[mask]==float("inf"):13continue14worker=bin(mask).count("1")# this many workers already assigned15ifworker==n:16continue17forjobinrange(n):18ifnot(mask&(1<<job)):# job still free
What interviewers expect you to know
Tricks to have memorized
x ^ x = 0, x ^ 0 = x — XOR cancels pairs (single number, missing number, swap without temp).
n & (n−1) clears the lowest set bit — powers of two, Kernighan's bit count.
1 << k builds a single-bit mask; & tests, | sets, & ~ clears, ^ toggles.
A bitmask is a set: | union, & intersection, ^ symmetric difference, all O(1).
Python-specific gotchas
Python ints are arbitrary precision — no overflow, but negative numbers have conceptually infinite leading 1s. Mask with & 0xFFFFFFFF to simulate 32-bit behavior.
bin(x).count('1') is a clean popcount; Python 3.10+ has int.bit_count().
& / | are bitwise; and / or are logical (short-circuiting) — a subtle bug source.
When to reach for bits
'Appears twice except one', 'find the missing/duplicate', 'without extra space' → XOR.
'Enumerate all subsets', 'state is a set of ≤ 20 things' → bitmask (possibly bitmask DP).
'Power of two', 'count bits', 'is only one bit set' → the n & (n−1) family.
Common mistakes
Bitwise vs logical operators
& and | operate on bits and don't short-circuit; and/or are boolean. Writing `if x and 1` when you meant `x & 1` is a classic mistake.
Negative numbers in Python
Right-shifting or counting bits of a negative int assumes infinite sign bits. Mask to the intended width (& 0xFFFFFFFF) before bit-counting fixed-size values.
XOR on the wrong variant
Plain XOR finds the single among PAIRS. If elements appear three times (except one), XOR fails — use per-bit counts mod 3.
Missing the power-of-two guard
n & (n−1) == 0 is also true for n = 0. Powers of two require n > 0.
Bitmask on large n
2ⁿ explodes: n = 30 is a billion masks. Bitmask techniques are for n ≲ 20.
Practice problems
Hand-picked from the 355-problem DSA Atlas. Reveal hints and solutions progressively; mark and bookmark as you solve.
5 questions across concepts, code output, complexity, and scenarios. Answer all, then submit for explanations.
Frequently asked questions
Do I really need bit manipulation for interviews?
For the common set — single number, power of two, counting bits, subsets — yes; these appear regularly and have clean bit solutions expected as the optimal answer. Deep bit-twiddling (advanced masks, gray codes) is rarer and role-dependent. Master the identities in the 'tricks to memorize' list and you'll handle most of it.
Why do negative numbers behave strangely with bitwise ops in Python?
Python ints are arbitrary precision and use two's-complement conceptually with infinite sign bits, so ~5 is −6 and right-shifting a negative keeps sign bits. When a problem assumes fixed 32-bit integers, mask with & 0xFFFFFFFF to emulate that width before counting or shifting.
What is bitmask DP and when is it used?
It's DP where a state includes 'which subset of ≤ ~20 items is done', encoded as one integer's bits. It compresses an exponential set-state into an array index, enabling problems like Traveling Salesman and assignment in O(2ⁿ · n) — feasible only for small n but otherwise intractable.
Summary & cheat sheet
Key takeaways
Bitwise operators act per bit: & masks, | sets, ^ flips/cancels, << >> scale by powers of two.
Memorize: x ^ x = 0, n & (n−1) clears the lowest set bit, 1 << k is a bit mask.
XOR solves 'single among pairs' and 'missing number' in O(1) space.
A bitmask is a set — O(1) union/intersection/membership; enumerate subsets over 0..2ⁿ−1.
In Python, mask to 32 bits when a problem assumes fixed-width integers.
Formulas & cheat sheet
test bit k: (x >> k) & 1 · set: x | (1<<k) · clear: x & ~(1<<k) · toggle: x ^ (1<<k)
power of two: n > 0 and (n & (n−1)) == 0
count bits DP: dp[i] = dp[i>>1] + (i & 1)
subset enumeration: for mask in range(1 << n)
Interview checklist
I can check/set/clear/toggle a bit from memory.
I can solve single-number and missing-number with XOR.
I can count set bits with Kernighan's method.
I can enumerate subsets with a bitmask and do O(1) set ops.