Bitmask Enumeration (Iterating Over Sets as Integers) — Interview Preparation¶
Bitmask enumeration is a high-frequency interview topic because it packs several crisp, testable ideas into a tiny amount of code: representing a subset of {0..n-1} as an integer, enumerating all 2^n subsets, iterating set bits with m & -m, walking submasks with (s-1) & mask, and recognizing the O(3^n) total. Interviewers use it to probe whether you can (a) pick the right loop for the shape of the problem, (b) reason about feasibility (2^n vs 3^n), (c) avoid the classic bugs (off-by-one on the full mask, the missing empty submask, the raw-~ complement), and (d) map set operations onto bitwise operations fluently. This file is a question bank by seniority, behavioral prompts, and four end-to-end coding challenges with runnable Go, Java, and Python solutions.
Quick-Reference Cheat Sheet¶
| Task | Tool | Complexity |
|---|---|---|
Enumerate all subsets of {0..n-1} | for mask in 0..2^n-1 | O(2^n) |
| Iterate present elements of a mask | while m: b=m&-m; m&=m-1 | O(popcount) |
| Iterate submasks of a fixed mask | s = (s-1) & mask | O(2^popcount) |
| Submasks over all masks | nested submask loop | O(3^n) |
| Iterate supersets of a fixed mask | s = (s+1) \| mask | O(2^(n-popcount)) |
| Aggregate over all submasks of all masks | SOS DP | O(n·2^n) |
| Fixed-size subsets in order | Gosper's hack (sibling 05) | O(C(n,k)) |
Set operations:
union A∪B a | b
intersection A∩B a & b
difference A\B a & ~b
symmetric diff a ^ b
complement (n) a ^ ((1<<n)-1) # NOT raw ~a
membership i∈A (a >> i) & 1
subset A⊆B (a & b) == a
size |A| popcount(a)
add / remove / toggle i a|(1<<i) / a&~(1<<i) / a^(1<<i)
Key facts: - Empty set = 0; full set = (1 << n) - 1; loop bound is mask < (1 << n). - m & -m = lowest set bit (a value); m &= m-1 clears it. - The submask loop while s: excludes 0 — handle it separately. - Σ_mask 2^popcount(mask) = 3^n (binomial theorem), so submasks-of-all-masks is O(3^n), not O(4^n). - Feasibility: 2^n to n ≈ 22–25; 3^n to n ≈ 16–18.
Junior Questions (12 Q&A)¶
J1. How do you represent a subset of {0..n-1} as an integer?¶
Set bit i to 1 iff element i is in the subset. So the integer is Σ_{i∈S} 2^i. For example 0b1010 = 10 represents {1, 3}.
J2. How do you enumerate all subsets?¶
for mask in range(1 << n) — the integers 0 .. 2^n - 1 are the 2^n subsets, each exactly once.
J3. How do you test whether element i is in mask?¶
mask & (1 << i) is non-zero iff bit i is set, i.e. i ∈ S. Equivalently (mask >> i) & 1.
J4. What does 1 << n equal, and why isn't it the full mask?¶
1 << n = 2^n, which is one past the last subset. The full set {0..n-1} is (1 << n) - 1 (all n low bits set). The loop bound is mask < (1 << n).
J5. How do you add / remove / toggle element i?¶
Add: mask | (1 << i). Remove: mask & ~(1 << i). Toggle: mask ^ (1 << i).
J6. What is the empty set and the full set as masks?¶
Empty set is 0; full set is (1 << n) - 1.
J7. How do you compute the size of the subset a mask represents?¶
popcount(mask) — the number of set bits. Use Integer.bitCount / bits.OnesCount / int.bit_count().
J8. How do you iterate only the elements that are present?¶
m = mask; while m: b = m & -m; i = log2(b); use i; m &= m - 1. Runs popcount(mask) times.
J9. What does m & -m give you?¶
The lowest set bit of m as a value (a power of two), e.g. 0b1100 & -0b1100 = 0b0100.
J10. Union and intersection of two sets as bitmasks?¶
Union is a | b; intersection is a & b.
J11. How many subsets does a set of n elements have?¶
2^n. Each element is independently in or out.
J12. What is the running example here and its complexity?¶
"Enumerate all subsets and their sums": loop all 2^n masks, sum the selected values. With a bit scan per mask it is O(2^n · n).
Middle Questions (10 Q&A)¶
M1. Explain the submask enumeration idiom and why it terminates.¶
s = mask; while s: process(s); s = (s-1) & mask. Each step strictly decreases s (since (s-1)&mask ≤ s-1 < s), so it reaches 0 and stops. It visits every submask once in decreasing order.
M2. Why is the empty submask not visited by while s:?¶
Because the loop condition is s != 0, the body never runs with s = 0. Handle 0 explicitly before or after, or use for(;;){ …; if(s==0) break; s=(s-1)&mask; }.
M3. Prove the submask loop visits each submask exactly once.¶
Identify mask's p set positions with a p-bit number (order-isomorphism). The step is a decrement on those positions, so it counts down through [0, 2^p) once each — every submask, no repeats.
M4. Why is "submasks of all masks" O(3^n) and not O(4^n)?¶
Σ_mask 2^popcount(mask) = Σ_k C(n,k) 2^k = (1+2)^n = 3^n by the binomial theorem. Equivalently, each element is in one of three states across a (mask, submask) pair: out of mask, in mask but out of submask, or in submask.
M5. How do you enumerate supersets of a mask?¶
s = mask; loop: process(s); if s==full break; s = (s+1) | mask. There are 2^(n-popcount(mask)) of them.
M6. How do you compute the complement of a set, and what's the trap?¶
mask ^ ((1<<n)-1). The trap is using raw ~mask, which flips all word bits (including positions ≥ n), giving a wrong/negative value.
M7. How would you enumerate only subsets of size k?¶
Either filter all 2^n masks by popcount == k, or use Gosper's hack to jump directly between equal-popcount masks in O(C(n,k)) (sibling 05).
M8. When do you prefer the lowbit loop over scanning all n bits?¶
When masks are sparse: the lowbit loop runs popcount times and is branchless, versus n iterations with a ~50%-mispredicted branch.
M9. What's the feasibility ceiling for 2^n and 3^n?¶
2^n to roughly n = 22–25; 3^n to roughly n = 16–18, given ~10^8–10^9 ops/sec.
M10. How does this connect to bitmask DP?¶
The mask becomes a DP state (which elements are used/covered). All-subsets and submask loops are the transition loops. See sibling 13/06-bitmask-dp.
Senior Questions (8 Q&A)¶
S1. What's the Java shift trap with masks?¶
<< on int masks the shift count mod 32, so 1 << 32 == 1 and 1 << 35 == 1 << 3. Use 1L << n for n ≥ 31 and assert n < 31 for int masks.
S2. What is the integer-width ceiling on n?¶
30 for signed 32-bit, 62–63 for 64-bit. Beyond 64 you need a bitset (array of words) and lose the single-instruction lowbit/submask tricks; but 2^n is hopeless there anyway.
S3. When should you avoid the O(3^n) submask loop entirely?¶
When you only need an aggregate (sum/min/max) over each mask's submasks — use SOS (sum-over-subsets) DP in O(n·2^n) instead.
S4. What's the meet-in-the-middle escape for large n?¶
Split the universe into two halves, enumerate 2^(n/2) subsets each, and combine — turning 2^40 into two 2^20 passes.
S5. How do you test enumeration code with 2^n outputs?¶
By properties and oracles: assert exact counts (2^n, 2^popcount, 3^n), uniqueness/coverage via a set, and compare submask output to the brute-force filter {t : t & mask == t}.
S6. What memory limit bites first in bitmask DP?¶
dp[2^n] of 8-byte entries: n=24 → 128MB, n=26 → 512MB. Memory often caps n before time does.
S7. Iteration order and cache — what matters?¶
Increasing-mask sweeps over dp[] are sequential and cache-friendly; submask loops scatter accesses, which is the real 3^n cost. SOS DP keeps the sweep sequential.
S8. What silent bugs pass small tests and break later?¶
Signed/wide shifts (1 << 31), raw-~ complement, missing empty submask, and under-sizing 3^n feasibility — all look fine on n ≤ 4.
Behavioral Prompts¶
- "Tell me about a time you optimized a brute-force solution." Frame: recognized the search was over subsets; replaced nested-loop/recursive include-exclude with a bitmask enumeration, then with bitmask DP, citing the
2^n/3^nfeasibility analysis you did up front. - "Describe a subtle bug you found in code review." Frame: a colleague used raw
~maskfor set complement; on a 6-element universe it leaked high bits and corrupted a subset comparison. You proposedmask ^ ((1<<n)-1)and a property testcomplement(complement(x)) == x. - "How do you communicate a tricky algorithm to teammates?" Frame: wrapped
(s-1)&maskin a documentedforEachSubmaskhelper with the one-line invariant and a count assertion, so the trick is explained once and reused safely. - "When did you choose not to use a clever trick?" Frame: needed an aggregate over submasks for
n=20;3^nwas 3.5 billion, so you used SOS DPO(n·2^n)instead — clever where it pays, simple where it doesn't.
Coding Challenges¶
Challenge 1 — Sum Over All Subsets¶
Problem. Given value[0..n-1], print the maximum subset sum that does not exceed a budget B (subset sums via full enumeration). n ≤ 20.
Go¶
package main
import "fmt"
func maxSumUnder(value []int, B int) int {
n := len(value)
best := 0
for mask := 0; mask < (1 << n); mask++ {
sum := 0
for i := 0; i < n; i++ {
if mask&(1<<i) != 0 {
sum += value[i]
}
}
if sum <= B && sum > best {
best = sum
}
}
return best
}
func main() {
fmt.Println(maxSumUnder([]int{3, 1, 4, 1, 5}, 9)) // 9
}
Java¶
public class MaxSumUnder {
static int maxSumUnder(int[] value, int B) {
int n = value.length, best = 0;
for (int mask = 0; mask < (1 << n); mask++) {
int sum = 0;
for (int i = 0; i < n; i++)
if ((mask & (1 << i)) != 0) sum += value[i];
if (sum <= B && sum > best) best = sum;
}
return best;
}
public static void main(String[] args) {
System.out.println(maxSumUnder(new int[]{3, 1, 4, 1, 5}, 9)); // 9
}
}
Python¶
def max_sum_under(value, B):
n = len(value)
best = 0
for mask in range(1 << n):
s = sum(value[i] for i in range(n) if mask & (1 << i))
if s <= B and s > best:
best = s
return best
if __name__ == "__main__":
print(max_sum_under([3, 1, 4, 1, 5], 9)) # 9
Challenge 2 — Partition Into Two Groups (Minimize Difference)¶
Problem. Split value[0..n-1] into two groups; minimize |sumA − sumB|. Enumerate every assignment of group membership as a mask. n ≤ 20.
Go¶
package main
import "fmt"
func minDiff(value []int) int {
n := len(value)
total := 0
for _, v := range value {
total += v
}
best := 1 << 62
for mask := 0; mask < (1 << n); mask++ {
a := 0
for i := 0; i < n; i++ {
if mask&(1<<i) != 0 {
a += value[i]
}
}
d := total - 2*a
if d < 0 {
d = -d
}
if d < best {
best = d
}
}
return best
}
func main() {
fmt.Println(minDiff([]int{3, 1, 4, 2, 2})) // 0
}
Java¶
public class MinDiff {
static int minDiff(int[] value) {
int n = value.length, total = 0;
for (int v : value) total += v;
int best = Integer.MAX_VALUE;
for (int mask = 0; mask < (1 << n); mask++) {
int a = 0;
for (int i = 0; i < n; i++)
if ((mask & (1 << i)) != 0) a += value[i];
best = Math.min(best, Math.abs(total - 2 * a));
}
return best;
}
public static void main(String[] args) {
System.out.println(minDiff(new int[]{3, 1, 4, 2, 2})); // 0
}
}
Python¶
def min_diff(value):
n = len(value)
total = sum(value)
best = float("inf")
for mask in range(1 << n):
a = sum(value[i] for i in range(n) if mask & (1 << i))
best = min(best, abs(total - 2 * a))
return best
if __name__ == "__main__":
print(min_diff([3, 1, 4, 2, 2])) # 0
Challenge 3 — Submasks for a Set Cover¶
Problem. Given a target mask and sets cover[j] (each a bitmask), count the submasks s ⊆ mask such that s equals the union of some single cover[j] intersected with mask. Simpler form: for a fixed mask, iterate all its submasks and count those that are also a superset of a given required core req ⊆ mask. Demonstrates the (s-1)&mask loop.
Go¶
package main
import "fmt"
func countSubmasksContaining(mask, req int) int {
count := 0
for s := mask; ; s = (s - 1) & mask {
if s&req == req { // req ⊆ s
count++
}
if s == 0 {
break
}
}
return count
}
func main() {
// submasks of 0b111 that contain 0b001: {001,011,101,111} => 4
fmt.Println(countSubmasksContaining(0b111, 0b001)) // 4
}
Java¶
public class SubmaskCover {
static int countSubmasksContaining(int mask, int req) {
int count = 0;
for (int s = mask; ; s = (s - 1) & mask) {
if ((s & req) == req) count++;
if (s == 0) break;
}
return count;
}
public static void main(String[] args) {
System.out.println(countSubmasksContaining(0b111, 0b001)); // 4
}
}
Python¶
def count_submasks_containing(mask, req):
count = 0
s = mask
while True:
if s & req == req: # req subset of s
count += 1
if s == 0:
break
s = (s - 1) & mask
return count
if __name__ == "__main__":
print(count_submasks_containing(0b111, 0b001)) # 4
Challenge 4 — Count Subsets With a Property¶
Problem. Count subsets of value[0..n-1] whose sum is divisible by k. Enumerate all 2^n subsets. n ≤ 20.
Go¶
package main
import "fmt"
func countDivisible(value []int, k int) int {
n := len(value)
count := 0
for mask := 0; mask < (1 << n); mask++ {
sum := 0
m := mask
for m != 0 {
i := m & -m // lowest set bit value
idx := 0
for (1 << idx) != i {
idx++
}
sum += value[idx]
m &= m - 1
}
if sum%k == 0 {
count++
}
}
return count
}
func main() {
fmt.Println(countDivisible([]int{1, 2, 3}, 3)) // subsets with sum%3==0: {},{3},{1,2} => 3
}
Java¶
public class CountDivisible {
static int countDivisible(int[] value, int k) {
int n = value.length, count = 0;
for (int mask = 0; mask < (1 << n); mask++) {
int sum = 0, m = mask;
while (m != 0) {
int idx = Integer.numberOfTrailingZeros(m);
sum += value[idx];
m &= m - 1;
}
if (sum % k == 0) count++;
}
return count;
}
public static void main(String[] args) {
System.out.println(countDivisible(new int[]{1, 2, 3}, 3)); // 3
}
}
Python¶
def count_divisible(value, k):
n = len(value)
count = 0
for mask in range(1 << n):
s, m = 0, mask
while m:
idx = (m & -m).bit_length() - 1
s += value[idx]
m &= m - 1
if s % k == 0:
count += 1
return count
if __name__ == "__main__":
print(count_divisible([1, 2, 3], 3)) # 3
Rapid-Fire Round (quick answers)¶
- Full mask for
n?(1 << n) - 1. - Loop bound for all subsets?
mask < (1 << n). - Lowest set bit value?
m & -m. - Clear lowest set bit?
m &= m - 1. - Index of lowest set bit? trailing-zero count of
m. - Complement within
n?mask ^ ((1<<n)-1), never~mask. - Number of submasks of
mask?2^popcount(mask). - Submasks of all masks total?
3^n. - Number of supersets of
maskinnbits?2^(n-popcount(mask)). - Empty set / full set?
0/(1<<n)-1. - Is
aa subset ofb?(a & b) == a. - Are
a,bdisjoint?(a & b) == 0. - Add element
i?mask | (1<<i). Remove?mask & ~(1<<i). Toggle?mask ^ (1<<i). - Why
3^nand not4^n? Three states per element across a (mask, submask) pair. - SOS DP cost?
O(n · 2^n). - Feasible
nfor2^n? ~22–25. For3^n? ~16–18.
Common Follow-Up Probes¶
Interviewers often deepen a question to test whether you actually understand the idiom rather than memorized it. Be ready for:
- "Walk me through
(s-1) & maskon a concrete mask." Takemask = 0b110.s=6 → (5)&6=4 → (3)&6=2 → (1)&6=0, then stop. That is{1,2},{2},{1},{}— all four submasks. Showing the trace convinces the interviewer instantly. - "What if I forget the
s == 0break?" Afters=0,(0-1)&mask = (-1)&mask = mask, so it wraps to the start and loops forever. The break is mandatory. - "How do you include the empty subset cleanly?" Use
for(;;){ work(s); if(s==0) break; s=(s-1)&mask; }so0is processed exactly once. - "Why is increasing-mask order safe for submask DP?" Every proper submask is a strictly smaller integer, so it is already computed when you reach
mask— increasing order is a topological order of the subset lattice. - "Scale it:
n = 40?"2^40is ~10^12, infeasible. Switch to meet-in-the-middle: split into two halves of 20, enumerate2^20each, combine.
Interview Tips¶
- State the representation first: "I'll encode each subset as an integer, bit
i= elementipresent." This signals fluency immediately. - Announce feasibility: quote
2^n/3^nand thenceiling before coding; it shows you sized the problem. - Pick the loop deliberately: all-subsets vs submasks vs supersets — say which and why.
- Call out the two classic bugs proactively: full mask is
(1<<n)-1, and thewhile s:loop omits the empty submask. - Mention the
3^ninsight when a submask-of-all-masks pattern appears — interviewers love the binomial / three-states explanation. - Offer the upgrade path: "if
nwere larger I'd use meet-in-the-middle or SOS DP" demonstrates range. - Use built-in popcount/trailing-zeros rather than hand loops; note the single-instruction cost.
- Trace a tiny case aloud (e.g. submasks of
0b110) when explaining(s-1)&mask; a concrete walk lands the point far better than the formula alone. - Name the lattice connection if pressed on ordering: increasing integer order is a topological sort of the subset lattice, so submask-dependent DP just sweeps
0 .. 2^n-1.
Mock Interview Walkthrough¶
A realistic 30-minute flow on Challenge 2 (two-group partition), showing how to narrate:
- Restate and clarify (1 min). "We split the array into two groups to minimize the absolute difference of sums. May elements be empty groups? Any size limit on
n?" — Interviewer:n ≤ 20, groups may be empty. - Identify the search space (2 min). "Each element independently goes to group A or B — that is a binary choice per element, so
2^nassignments. Withn ≤ 20that is about a million, well within budget. I'll encode each assignment as a bitmask: bitiset means elementiis in group A." - State the formula (1 min). "If
ais group A's sum, group B's sum istotal - a, and the difference is|total - 2a|. So I just minimize that over all masks." - Code it (8 min). Write the all-subsets loop with a lowbit inner accumulation; mention
long/int64for the sums. - Test (4 min). "Balanced multiset
[3,1,4,2,2]totals 12; a0difference partition exists, so the answer should be0." Trace one mask to confirm the formula. - Discuss scaling (4 min). "At
n = 40this2^40is infeasible; I'd meet-in-the-middle: enumerate2^20subset sums of each half, sort one half, and binary-search the closest complement —O(2^{n/2} · n/2)." - Edge cases (2 min). Empty input, all-zero values, a single element (difference is that element).
The pattern generalizes: restate → size the search space → encode as masks → state the per-mask formula → code → test with a known answer → discuss the scaling escape hatch.
Pitfalls Interviewers Watch For¶
- Using
1 << nas the full mask (off by the top bit) or as the loop's inclusive bound. - Writing
~maskfor complement and not masking tonbits. - A submask loop that omits
0when the empty subset is part of the answer, or that never terminates. - Quoting
O(4^n)for the submask double loop instead of the correctO(3^n). - Recomputing a subset's sum from scratch when a lowbit or Gray-code incremental update would do.
- Ignoring overflow on subset sums (32-bit) for large values.
Summary¶
Interviewers reward one crisp insight — a subset is an integer, bit i = element i — and then test whether you can choose among the three loops (all subsets 2^n, set bits popcount, submasks 2^popcount), reason about feasibility (2^n to ~22, 3^n to ~17), map set algebra to bitwise ops (with mask ^ ((1<<n)-1) for complement, never raw ~), and dodge the classic bugs (full mask (1<<n)-1, the missing empty submask, Java's shift-count masking). Know the 3^n = Σ C(n,k)2^k derivation cold, and know when to escape to SOS DP (O(n·2^n)) or meet-in-the-middle. The four challenges above — subset sums, two-group partition, submask cover counting, and counting subsets by property — exercise every idiom in Go, Java, and Python.