Bitmask DP
Use the bits of a number to record which items of a small set are already used, and keep a DP answer for every such set.
O(2ⁿ × n)Space O(2ⁿ)5 practice problemsStep-through pictureThe idea
Some problems need to remember which items are already used, not just how many. With up to about 20 items, a set of items fits in the bits of one number, called a bitmask. Bit t is 1 if item t is in the set. For 3 items, the mask 0b101 (the number 5) means items 0 and 2 are used.
Bitmask DP keeps one answer per mask. Here best[mask] is the cheapest cost to assign the tasks in mask, one each, to the first few people. From each mask you try adding one more unused item, which gives a bigger mask.
There are 2^n masks, so this only works for small n: 2^20 is about a million, which is fine; 2^40 is not.
An everyday example
A seating chart with checkboxes
You are seating guests at a table one at a time and each guest has a different comfort score for each seat. A row of checkboxes shows which seats are taken. The next guest to seat is always the next one on the list, so the checkboxes alone tell you who is next. For every pattern of checkboxes, you write down the best total comfort to reach it.
| In the example | In the code |
|---|---|
| The row of checkboxes | The bits of mask |
Seat t is taken | mask & (1 << t) is not zero |
| How many guests are seated | countBits(mask), the next person |
Seating the next guest in seat t | next = mask | (1 << t) |
| Best total for a checkbox pattern | best[mask] |
From the slow way to bitmask dp
- Start with brute force: try every way to give each person one task, add up each assignment's cost, and keep the cheapest. With
npeople there aren!assignments: 3,628,800 for 10 people and 1,307,674,368,000 for 15. - Build an assignment one person at a time, in a fixed order. After the first
kpeople have a task, what matters for the rest? Only which tasks are taken. Whether person 0 took task 2 and person 1 took task 5, or the other way around, the remaining people face the same choices. So only the cheaper of those two beginnings is worth keeping. - The state is the set of taken tasks. With
ntasks there are2^nsets, and each set fits in one number: bittis 1 if tasktis taken. Adding tasktismask | (1 << t), and checking it ismask & (1 << t). The number of 1 bits tells you how many people are done, so it also tells you who is next. - Store
best[mask], the cheapest cost to give the tasks inmaskto the first few people. Go through masks from 0 upward, and from each reachable mask, try giving the next person each free task. Adding a bit always makes a bigger number, so every mask is final before the loop reaches it. That loop is bitmask DP.
How it works
- Make
bestwith2^ncells, allInfinity, and setbest[0] = 0: no tasks assigned costs nothing. - Go through masks from
0upward. A mask's tasks are assigned to people0tocountBits(mask) - 1, soperson = countBits(mask)is next. - Skip masks whose
bestis stillInfinity; they can't be reached. - For each
tasknot inmask, formnext = mask | (1 << task). - Update
best[next]withbest[mask] + cost[person][task]if it is cheaper. - The answer is
best[full], wherefullhas every bit set.
Watch it run
This picture runs the code below on a real input. Press Step to move one step at a time, or Play to watch. The highlighted line is the one that just ran. Change the input to see other cases.
See every step at onceThe same run, laid out side by side
The code
Point at a note to light up the lines it explains.
function minAssignCost(cost) {const n = cost.length;const full = (1 << n) - 1;const best = new Array(1 << n).fill(Infinity);best[0] = 0;for (let mask = 0; mask < full; mask++) {if (best[mask] === Infinity) continue;const person = countBits(mask);for (let task = 0; task < n; task++) {if (mask & (1 << task)) continue;const next = mask | (1 << task);best[next] = Math.min(best[next], best[mask] + cost[person][task]);}}return best[full];}function countBits(mask) {let count = 0;for (; mask; mask &= mask - 1) count++;return count;}
How fast it is
| Time | O(2ⁿ × n) |
|---|---|
| Extra memory | O(2ⁿ) |
Every one of the 2^n masks tries each of the n tasks once. The table has one cell per mask.
- Best case
O(n · 2^n)Any cost table: from the empty mask every other mask is reachable, so every mask tries every task.
- Average case
O(n · 2^n)Any cost table:
2^nmasks, each checkingnbits. For 15 people that is 491,520 steps. - Worst case
O(n · 2^n)The same. The costs never change which masks are visited, only which updates win.
Common mistakes
- Using it for 30 or more items.
2^30cells is about a billion; check thatnis small first. - Writing
mask & 1 << task === 0. In JavaScript===binds tighter than&, so add parentheses:(mask & (1 << task)) === 0. - Adding a person to the state when the mask already tells you who is next, which makes the table much bigger for no reason.
- Using
1 << 31or more. JavaScript's bit operators work on 32-bit signed numbers, so1 << 31is negative.
When to use it
Look for these clues in a problem:
- The input has a small number of items, usually 20 or fewer.
- The answer depends on which items were used, not just how many.
- The question is about visiting every place once, assigning every task once, or covering every requirement.
- A plain search over all orders (
n!) is too slow, but2^n * nis fine.
Check yourself
Answer each one in your head first, then open it.
Why does going through masks in increasing order work?
Adding a task sets a bit, which always makes a bigger number. So every update into a mask comes from a smaller mask, which the loop has already handled.
How does the code know which person is next?
It counts the 1 bits in mask. People are assigned in order, so k taken tasks means people 0 to k - 1 are done and person k is next.
In the trace, why does mask 101 drop from 10 to 9?
Two orders fill tasks 0 and 2: person 0 on task 0 and person 1 on task 2 cost 3 + 7 = 10, while person 0 on task 2 and person 1 on task 0 cost 4 + 5 = 9.
How many masks are there for 12 tasks?
2^12 = 4,096, from 0 (none taken) to 4,095 (all taken).
Practice problems
Stuck on one? Tuto is in the playground too, and gives you hints, never the answer.
0 of 5 solved. Solve one to earn the bronze medal, half for silver, and all of them for gold.
Start practicingGo further
You don't need these to start practicing. Open one when you want more.
Why it works
The reasoning that makes it always right
The table keeps one promise: when the loop reaches mask, best[mask] is the cheapest cost to give exactly the tasks in mask to people 0 through k - 1, where k is the number of 1 bits. best[0] = 0 is right: no people, no tasks, no cost.
The promise holds because of where updates come from. A cheapest assignment for mask gives person k - 1 some task t in mask. The other people hold the tasks in mask without t, a smaller number, and they must be assigned as cheaply as possible; otherwise a cheaper arrangement could be swapped in. When the loop handled that smaller mask, it tried giving person k - 1 task t, so best[mask] has already seen the right value.
Every update goes from a mask to a strictly bigger one, so processing masks in increasing order means each mask has received every update before it pushes its own. Masks still at Infinity can't be reached and are skipped. The loop stops before full, and the promise says best[full] is the cheapest way to give all n tasks to all n people.
Going deeper
4 more ideas
What each cell means
best[mask] is the cheapest total cost to give the tasks in mask to people 0, 1, ..., k - 1, where k is the number of 1 bits in mask. The people are always taken in order, so the mask alone tells you who is next.
Why going through masks in increasing order works
Adding a task sets a bit, which always makes the number bigger. So every mask is only updated from smaller masks, and by the time the loop reaches a mask, all its updates are done.
How much work it does
There are 2^n masks and each tries n tasks, so the time is O(2^n * n) with O(2^n) space. For n = 15 that is about half a million steps. Trying every order instead would be 15!, more than a trillion.
Visiting every place (tours)
For the shortest route through every place, the state needs one more piece: where you are now. best[mask][last] is the shortest route that visits the places in mask and ends at last. That is O(2^n * n^2) time.
Work it by hand
Every step in a table, with why
Give 3 people one task each at the least total cost. Masks are written with task 2 on the left, so 001 means only task 0 is taken. Each row is one update.
cost = [[3, 8, 4], [5, 2, 7], [6, 9, 3]], where row p is person p
| Step | mask | best[mask] | person | task | next | best[next] | What happens | Why |
|---|---|---|---|---|---|---|---|---|
| 1 | 000 | 0 | 0 | 0 | 001 | ∞ to 3 | First option | From the empty mask, person 0 takes task 0 for 3. |
| 2 | 000 | 0 | 0 | 1 | 010 | ∞ to 8 | Person 0, task 1 | Person 0 takes task 1 for 8. |
| 3 | 000 | 0 | 0 | 2 | 100 | ∞ to 4 | Person 0, task 2 | Every one-task mask is now reachable. |
| 4 | 001 | 3 | 1 | 1 | 011 | ∞ to 5 | 3 + 2 | One bit is set, so person is 1. Task 0 is taken, so person 1 tries tasks 1 and 2. |
| 5 | 001 | 3 | 1 | 2 | 101 | ∞ to 10 | 3 + 7 | A first value for mask 101, which a later row will beat. |
| 6 | 010 | 8 | 1 | 0 | 011 | 5, keep 5 | 8 + 5 = 13 loses | Line 12 keeps the smaller value. The other order of tasks 0 and 1 was cheaper. |
| 7 | 010 | 8 | 1 | 2 | 110 | ∞ to 15 | 8 + 7 | A first value for mask 110. |
| 8 | 011 | 5 | 2 | 2 | 111 | ∞ to 8 | 5 + 3 | Two bits set, so person 2 is next, and only task 2 is free. The full mask gets its first value. |
| 9 | 100 | 4 | 1 | 0 | 101 | 10 to 9 | 4 + 5 is better | Person 0 on task 2 and person 1 on task 0 beat the earlier way to fill tasks 0 and 2. |
| 10 | 100 | 4 | 1 | 1 | 110 | 15 to 6 | 4 + 2 is better | A big drop: person 1 is cheap on task 1. |
| 11 | 101 | 9 | 2 | 1 | 111 | 8, keep 8 | 9 + 9 = 18 loses | Person 2 is expensive on task 1. |
| 12 | 110 | 6 | 2 | 0 | 111 | 8, keep 8 | 6 + 6 = 12 loses | The last update. The loop stops before mask 111, since mask < full is false. |
It returns 8: person 0 on task 0, person 1 on task 1, person 2 on task 2, for 3 + 2 + 3. The loop ends when mask reaches full, because the full mask has no free task to add.
Compared with other ways
When to pick something else
| Approach | Time | Extra memory | Choose it when |
|---|---|---|---|
| Bitmask DP | O(n · 2^n) | O(2^n) | Up to about 20 items, when the answer depends on which items are used, not on their order. |
| Try every order | O(n!) | O(n) | Only for about 10 items or fewer, or as a check. |
| Hungarian algorithm | O(n³) | O(n²) | Pure assignment problems with many people, such as 500. It solves this exact problem much faster, but doesn't generalize to tours or groups. |
| Backtracking with pruning | O(n!) | O(n) | When good bounds cut most branches early. Its worst case stays factorial. |
Extra memory: O(2^n) for best: about a million numbers for 20 tasks. That memory limit is usually what stops bitmask DP, not the time.
How the steps grow
- Bitmask DP
O(n · 2^n) - Try every order, Backtracking with pruning
O(n!) - Hungarian algorithm
O(n³)
| Approach | n = 10 | n = 1,000 | n = 1,000,000 |
|---|---|---|---|
| Bitmask DP | 10,240 | more than a quadrillion | more than a quadrillion |
| Try every order | 4 million | more than a quadrillion | more than a quadrillion |
| Hungarian algorithm | 1,000 | 1 billion | more than a quadrillion |
| Backtracking with pruning | 4 million | more than a quadrillion | more than a quadrillion |
Inputs that need care
5 tricky inputs
- No people:
cost = [] fullis 0, so the loop never runs, and it returnsbest[0], which is 0.- One person:
cost = [[6]] - Mask 0 tries task 0 and sets
best[1] = 6. The loop stops atfull = 1and returns 6. - A forbidden pairing, written as
Infinity best[mask] + InfinityisInfinity, so it never wins. If every full assignment is forbidden, it returnsInfinity; turn that into-1if the problem wants that.- Ties between assignments
Math.minkeeps the cost, and any tied assignment is fine. To report the assignment, keep the first one found, or decide on another rule.- 31 or more tasks
1 << 31is negative in JavaScript, and2^31cells won't fit in memory anyway. Bitmask DP is for about 20 items or fewer.
Variations and follow-ups
Twists interviewers add
Shortest tour through every place
For a route that visits every place once, the next step depends on where you are, so the state is best[mask][last]. Each state tries every unvisited place, which gives O(2^n · n²) time.
Looping over submasks
Some problems split a set into groups. For each mask, loop over its submasks with sub = (sub - 1) & mask. Across all masks, that is 3^n steps in total, which is fine for n up to about 15.
More people than tasks, or the reverse
Put the smaller side in the mask. If there are 8 tasks and 50 people, loop over people one at a time and keep best[mask] for which tasks are filled, letting each person take one free task or none.
Memoized version
Write solve(mask) that tries every free task for the next person and caches the result. It only visits reachable masks, which helps when many masks are impossible.
In an interview
Spot it, explain it, answer follow-ups
- Spot it from a tiny limit, like
n ≤ 16orn ≤ 20, together with "each item used once" or "visit every place". A limit that small is a strong hint that2^nis expected. - Explain the mask in one sentence: "bit
tis 1 if tasktis taken". Then say why the person doesn't need to be in the state: the number of 1 bits tells you who is next. - Test a 2 by 2 cost table by hand, where you can list both assignments, and check that the increasing mask order fills
11last. - Expect "how much memory?" and "how far can
ngo?".2^20cells is about a million numbers, fine;2^30is a billion, too many. Also mention that JavaScript bit operators stop working at 31 bits.
Where frontend code uses it
The same idea in real UI code
- Route planners that order a handful of stops, such as a delivery app with 10 drops, can find the shortest order with a bitmask table.
- Assigning a few reviewers to a few pull requests, or a few people to shifts, with the least total cost fits this table.
- Feature flags are often stored as bits in one number, the same representation bitmask DP uses for sets.