Bitwise Operators for Flags
Using & | ^ ~ << for permission flags and packed options — int32 traps, readability tradeoffs, and clearer alternatives.
- javascript
- bitwise
- flags
- permissions
Bitwise flags pack many booleans into one integer. You will meet them in file modes, canvas bitfields, and interview questions. You rarely need them for app feature toggles — a Set of strings is clearer — but you should read and write them without sweating.
Operators that matter
| Op | Meaning (per bit) |
|---|---|
a | b |
set bits that are in either |
a & b |
bits in both |
a ^ b |
bits in one but not both |
~a |
flip all bits (see int32 note) |
a << n |
shift left (multiply by 2^n) |
a >> n |
arithmetic shift right |
a >>> n |
zero-fill shift right |
const READ = 1 << 0; // 0001
const WRITE = 1 << 1; // 0010
const EXEC = 1 << 2; // 0100
const ALL = READ | WRITE | EXEC;
let perms = READ | EXEC; // 0101
// check
(perms & WRITE) !== 0; // false
(perms & READ) !== 0; // true
// add
perms = perms | WRITE;
// remove
perms = perms & ~WRITE;
// toggle
perms = perms ^ EXEC;
Int32 coercion (the footgun)
All JS bitwise ops convert operands to signed 32-bit integers (except when you use BigInt). High bits vanish.
const big = 2 ** 40;
big | 0; // 0 — not what you wanted
// for wide flags, use BigInt
const FLAG = 1n << 40n;
const pack = FLAG | 1n;
(pack & FLAG) !== 0n; // true
~x is -(x + 1) for numbers in practice:
~0; // -1
~1; // -2
// masking after ~ often uses >>> 0 for unsigned 32-bit view
(~READ) >>> 0;
Compact option bags
const HAS_CHILDREN = 1 << 0;
const IS_EXPANDED = 1 << 1;
const IS_SELECTED = 1 << 2;
function encodeNode({ hasChildren, expanded, selected }) {
let f = 0;
if (hasChildren) f |= HAS_CHILDREN;
if (expanded) f |= IS_EXPANDED;
if (selected) f |= IS_SELECTED;
return f;
}
function isSelected(flags) {
return (flags & IS_SELECTED) !== 0;
}
Useful when serializing millions of tree nodes. Overkill for a React props object.
Enumerating set bits
function* setBits(n) {
let i = 0;
n >>>= 0; // force uint32
while (n) {
if (n & 1) yield i;
n >>>= 1;
i++;
}
}
[...setBits(0b1011)]; // [0, 1, 3]
When not to use flags
// clearer for app code
const permissions = new Set(['read', 'write']);
permissions.has('write');
permissions.add('exec');
permissions.delete('write');
Flags win when: interop with native APIs, dense storage, or hot bit tests. They lose when: you need names in logs, partial updates from APIs, or more than 32 distinct bits without BigInt.
Interview answer
“I assign each flag a power of two, combine with |, test with &, clear with & ~flag, toggle with ^. JS bitwise ops use signed 32-bit ints, so I switch to BigInt for wider packs. For product code I often prefer a Set of permission strings unless density or interop requires bits.”
Related
Further reading
Related guides
- AbortControllerCancel fetch and other async work with AbortController and AbortSignal — timeouts, race conditions, and cleanup when components unmount.
- Array find, some, every, includesShort-circuiting array predicates: find, findIndex, some, every, and includes — when to use each and common interview traps.
- Array map, filter, reducemap, filter, and reduce as the core transform toolkit — immutability, chaining costs, reduce patterns, and when a plain loop is clearer.
- Arrow Functions Deep DiveLexical this, no arguments object, no construct, concise bodies — when arrows help and when methods and generators need classic functions.
- Async Iteration and for await...ofAsync iterables, for await...of, and streaming data — how async iterators differ from Promise.all and when to use each.