Bit flags
Store many on/off options in one number, one bit per option, and grant, revoke, toggle, or check them with single bitwise operations.
O(1)Space O(1)7 practice problemsStep-through pictureThe idea
A bit flag gives each option its own bit: READ is bit 0 (value 1), WRITE is bit 1 (value 2), DELETE is bit 2 (value 4), and so on. A set of options is then one number that adds up the bits that are on: read plus write is 1 | 2 = 3.
Every operation is one bitwise step. Grant a flag with mask | flag. Revoke it with mask & ~flag. Toggle it with mask ^ flag. Check that all of some flags are on with (mask & flags) === flags, or that any of them is on with (mask & flags) !== 0.
This is compact (up to 31 options in one number in JavaScript), fast, and easy to compare or store. Browsers, graphics APIs, compilers, and React itself use flags this way.
An everyday example
A key ring with labeled hooks
A key ring has labeled hooks: hook 0 is the front door, hook 1 the garage, hook 2 the office, hook 3 the mailbox. Looking at the ring, you can see which keys someone holds. Handing over a key fills a hook, taking it back empties it, and asking "can they open the garage and the office?" means checking two hooks at once.
| In the example | In the code |
|---|---|
| A hook with its label | Permission.WRITE, which is 1 << 1 |
| The whole key ring | mask |
| Handing over a key | grant(mask, flag), which is mask | flag |
| Taking a key back | revoke(mask, flag), which is mask & ~flag |
| Checking for several keys at once | (mask & flags) === flags |
From the slow way to bit flags
- Start with the obvious way to store permissions: an object of booleans, like
{ read: true, write: false, delete: true, share: false }. It's readable, but checking "has read and delete" needs two lookups, comparing two permission sets needs a loop over the keys, and storing it in a URL or a database takes a lot more space than a number. - Give each option its own bit instead:
READis1,WRITEis2,DELETEis4,SHAREis8. Each value is a power of two, so each has a single 1 bit in its own position. A set of options becomes one number whose bits say which options are on: read plus delete is0101, which is 5. - Now every set operation is one operator. Adding options is OR, removing them is AND with the flipped flags, and toggling is XOR. Because the operators work on all bits at once, you can grant or check several flags in one step, such as
READ | DELETE. - Checking needs one more idea:
mask & flagskeeps only the asked-about bits that are on. If that equalsflags, all of them are on; if it's not 0, at least one is. Name the powers of two in one object and wrap each operation in a tiny function. Those functions are bit flags.
How it works
- Give each option its own power of two:
1 << 0,1 << 1,1 << 2, and so on. - Combine options with
|:Permission.READ | Permission.WRITEis3. - Grant with
mask | flag, revoke withmask & ~flag, and toggle withmask ^ flag. - Check that every flag in
flagsis on with(mask & flags) === flags. - Check that at least one is on with
(mask & flags) !== 0.
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.
const Permission = {READ: 1 << 0,WRITE: 1 << 1,DELETE: 1 << 2,SHARE: 1 << 3,};function grant(mask, flag) {return mask | flag;}function revoke(mask, flag) {return mask & ~flag;}function toggle(mask, flag) {return mask ^ flag;}function hasAll(mask, flags) {return (mask & flags) === flags;}function hasAny(mask, flags) {return (mask & flags) !== 0;}
How fast it is
| Time | O(1) |
|---|---|
| Extra memory | O(1) |
Each operation is one bitwise step on one number, however many flags are combined.
- Best case
O(1)Any call: one bitwise operation, and a comparison for the checks.
- Average case
O(1)The same single step whether
flagsnames one option or ten. - Worst case
O(1)Still one step with all 31 safe flags combined. An object of booleans would need one lookup per flag instead.
Common mistakes
- Numbering flags
1, 2, 3, 4instead of1, 2, 4, 8. Then3is both a flag and1 | 2, and checks give wrong answers. - Checking several flags with
mask & flagsas a boolean. That is true if any of them is on; use=== flagswhen all must be on. - Revoking with
mask ^ flag. If the flag was off, XOR turns it on. Usemask & ~flag. - Defining more than 31 flags.
1 << 31is negative and1 << 32wraps around to1.
When to use it
Look for these clues in a problem:
- The problem has a fixed, small set of yes/no options: permissions, features, modifier keys, states.
- Options are combined, compared, or stored and sent as one value.
- You need fast checks like "has all of these" or "has any of these".
- The statement uses words like flags, mask, lanes, or bit field.
Check yourself
Answer each one in your head first, then open it.
Why must every flag be a power of two?
So each has one 1 bit in its own position. If flags were 1, 2, 3, then 3 would look exactly like 1 | 2, and you couldn't tell them apart.
What does revoke(13, WRITE) return, and what would 13 ^ WRITE give?
revoke returns 13, because WRITE was already off. 13 ^ 2 gives 15, turning WRITE on by mistake.
Why does hasAll compare with === flags instead of just checking mask & flags?
mask & flags is non-zero when any of the flags is on. Only when it equals flags are all of them on: 5 & 3 is 1, not 0, yet WRITE is off.
What does hasAll(5, 0) return?
true, because 5 & 0 is 0, which equals the flags value 0. Checking an empty list of flags always passes.
Practice problems
Stuck on one? Tuto is in the playground too, and gives you hints, never the answer.
0 of 7 solved. Solve one to earn the bronze medal, half for silver, and all of them for gold.
Start practicingmedium
Go 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
Each flag is a different power of two, so each has exactly one 1 bit, and no two share a position. ORing flags together never carries from one position to another, so the resulting mask has a 1 exactly at the positions of the chosen flags. Different sets of flags always give different numbers, and every flag can be read back.
Each operation changes only the positions named in flag. OR with 0 keeps a bit and OR with 1 sets it, so grant turns on the flag bits and leaves others alone. ~flag has 0 at the flag positions and 1 elsewhere, so revoke clears exactly those positions. XOR with 1 flips and XOR with 0 keeps, so toggle flips exactly those positions.
mask & flags keeps a bit only where both have a 1, so it's the set of asked-for flags that are actually on. If every asked-for flag is on, that's all of flags, which is what hasAll compares with. If any is on, the result has at least one 1 bit and isn't 0, which is what hasAny checks.
Going deeper
3 more ideas
Why each flag needs its own bit
If two options shared a bit, turning one on would look the same as turning the other on. Powers of two each have a single 1 bit in a different place, so any combination of them gives a unique number, and every option can be read back with &.
How React uses flags and lanes
Inside React, each fiber (the object React keeps for every rendered component) has a flags field: a bit field saying what work it needs, such as placement or update. React also tracks update priorities as lanes, which are bits in a 31-bit number. Pending work is the OR of lanes, and a lower bit means a higher priority, so React can pick the most urgent lane with lanes & -lanes, which keeps only the lowest set bit.
Limits and alternatives
JavaScript's bitwise operators use 32-bit signed integers, so at most 31 flags are safe if you want to avoid the sign bit. For more, use several numbers, a BigInt, or a Set of names. A Set is easier to read, so use flags when you need compact storage, fast combining, or an API that expects them.
Work it by hand
Every step in a table, with why
Change one permission mask through a series of calls, including two that change nothing, and check it four ways.
mask = 0, with READ = 1, WRITE = 2, DELETE = 4, SHARE = 8
| Step | Call | mask | Bits (SDWR) | What happens | Why |
|---|---|---|---|---|---|
| 1 | start | 0 | 0000 | no permissions | The bits are written as SHARE, DELETE, WRITE, READ from left to right. |
| 2 | grant(mask, SHARE) | 8 | 1000 | 0 | 8 | OR turns on bit 3 and leaves the rest alone. |
| 3 | grant(mask, DELETE | READ) | 13 | 1101 | 8 | 5 | DELETE | READ is 0101, so one call grants two flags. |
| 4 | grant(mask, READ) | 13 | 1101 | already on: no change | OR with a bit that's already 1 keeps it 1. Granting twice is safe. |
| 5 | revoke(mask, WRITE) | 13 | 1101 | already off: no change | ~2 is all ones except bit 1, so AND keeps WRITE off. toggle here would have turned it on, giving 15. |
| 6 | toggle(mask, SHARE) | 5 | 0101 | 13 ^ 8 | XOR flips bit 3 from on to off. |
| 7 | hasAll(mask, READ | DELETE) | 5 | 0101 | true | 5 & 5 is 5, which equals the flags asked for: both are on. |
| 8 | hasAll(mask, READ | WRITE) | 5 | 0101 | false | 5 & 3 is 1: READ is on but WRITE isn't, so the result isn't 3. |
| 9 | hasAny(mask, WRITE | SHARE) | 5 | 0101 | false | 5 & 10 is 0: neither flag is on. |
| 10 | hasAny(mask, READ | SHARE) | 5 | 0101 | true | 5 & 9 is 1, which isn't 0: at least one of them, READ, is on. |
| 11 | revoke(mask, READ | DELETE) | 0 | 0000 | 5 & ~5 | Revoking both remaining flags at once leaves no permissions. |
The mask ends at 0. Each call was one bitwise operation on one number, however many flags it touched, and granting an on flag or revoking an off flag changed nothing.
Compared with other ways
When to pick something else
| Approach | Time | Extra memory | Choose it when |
|---|---|---|---|
| Bit flags | O(1) | O(1) | Up to 31 fixed options that you combine, compare, or store often: permissions, render state, feature switches. |
| Object of booleans | O(k) | O(k) | A few options where readable code and JSON matter more than speed; checking k flags takes k lookups. |
Set of names | O(k) | O(k) | Options that change at runtime or number more than 31. |
Extra memory: O(1): one number holds up to 31 flags, 4 bytes of information.
How the steps grow
- Bit flags
O(1) - Object of booleans,
Setof namesO(k)
| Approach | n = 10 | n = 1,000 | n = 1,000,000 |
|---|---|---|---|
| Bit flags | 1 | 1 | 1 |
| Object of booleans | 10 | 1,000 | 1 million |
Set of names | 10 | 1,000 | 1 million |
Inputs that need care
5 tricky inputs
- Checking no flags:
hasAll(mask, 0)andhasAny(mask, 0) hasAllis alwaystrue(every flag in an empty list is on) andhasAnyis alwaysfalse. Ifflagsis built from user input, decide what an empty request should mean.- A misspelled flag:
Permission.EDIT, which isundefined grant(5, undefined)returns 5 without any error, becauseundefinedbecomes 0.hasAll(5, undefined)isfalse. Define flags in a frozen object or a TypeScript enum so typos fail loudly.- Revoking a flag that is already off:
revoke(13, WRITE) - Returns 13, unchanged. Using
13 ^ WRITEinstead would wrongly turn WRITE on, giving 15. - A 32nd flag:
1 << 31and1 << 32 1 << 31is negative, so tests likemask > 0break;1 << 32wraps to 1 and collides with READ. Stop at 31 flags, or switch toBigIntor several numbers.- Stored masks after the flags change
- A mask saved as
13means "SHARE, DELETE, READ" only while those bit positions keep their meaning. Never renumber existing flags; only add new ones in new positions.
Variations and follow-ups
Twists interviewers add
Named combinations
Define common groups as flags too: EDITOR = READ | WRITE is 3, and ADMIN = READ | WRITE | DELETE | SHARE is 15. hasAll(mask, EDITOR) then checks a whole role in one step.
Priorities as bits
React keeps pending update priorities as bits called lanes, where a lower bit means more urgent. The most urgent pending one is the lowest set bit, lanes & -lanes, found in one step without a loop.
Listing and counting flags
To show a mask to a person, loop over the flag object and keep the names where hasAll(mask, value) is true. To know how many flags are on, count its 1 bits.
A Set of names
new Set(["read", "delete"]) is easier to read and has no 31-item limit. It's the better choice when compact storage and one-step checks don't matter.
In an interview
Spot it, explain it, answer follow-ups
- Spot it from "permissions", "feature switches", "a set of states that can combine", "store several booleans compactly", or an API that takes options like
READ | WRITE. - Explain the encoding first: "each option is a power of two, so it has its own bit". Then each operation is one line: OR to grant, AND NOT to revoke, XOR to toggle, AND then compare to check.
- Test granting a flag that's already on, revoking one that's already off, and checking several flags at once, both all and any. These show why
revokecan't use XOR and whyhasAllneeds=== flags. - Expect "what if we need 40 flags?". Answer: JavaScript's bitwise operators are 32-bit, so use
BigInt, two numbers, or aSet, and explain the trade-off in readability.
Where frontend code uses it
The same idea in real UI code
- React stores each fiber's pending work in a
flagsbit field and update priorities as lanes, which are bits where a lower bit means a more urgent update. MouseEvent.buttonsis a bit field:1is the primary button,2the secondary,4the middle, so(event.buttons & 2) !== 0checks the right button.document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT)picks which node types to visit with flags.- The TypeScript compiler marks types and symbols with flag enums such as
TypeFlags, checked with&.