Bitwise Operators
Bitwise operators work on the binary representation of numbers. Most JavaScript you write will never touch them — string parsing, network requests and DOM manipulation rarely call for bit twiddling. They become genuinely useful in three places: bit flags, fast integer tricks, and code that interoperates with binary formats (TypedArrays, canvases, codecs). Worth knowing they exist, worth recognising them on sight.
The 32-bit truth
JavaScript numbers are 64-bit floats, but bitwise operators first convert their operands to a 32-bit signed integer, do the operation, then convert back to a regular number. That conversion is the source of a few surprises with very large numbers.
(2 ** 31) | 0; // -2147483648 (overflow into negative) (2 ** 32) | 0; // 0 (any bit above the 32nd is dropped) 3.9 | 0; // 3 (handy way to truncate toward zero) -3.9 | 0; // -3
AND, OR, XOR, NOT
0b1100 & 0b1010; // 0b1000 = 8 (AND — bits set in BOTH) 0b1100 | 0b1010; // 0b1110 = 14 (OR — bits set in EITHER) 0b1100 ^ 0b1010; // 0b0110 = 6 (XOR — bits set in exactly one) ~0; // -1 (NOT — flip every bit) ~5; // -6 (~n === -(n+1)) ~~3.9; // 3 (double NOT — another truncate trick)
The relationship ~n === -(n+1) falls out of two's-complement representation. ~~value is a popular shorthand for "convert to a 32-bit integer", though Math.trunc(value) reads more clearly.
Shift operators
1 << 3; // 8 (1 shifted left 3 places — same as 1 * 2^3) 16 >> 2; // 4 (16 shifted right 2 — same as 16 / 4, integer) -16 >> 2; // -4 (signed: copies the sign bit) -16 >>> 2; // 1073741820 (unsigned: fills with zeros, treats as unsigned 32-bit)
<<— left shift. Multiplies by2^n(within 32 bits).>>— sign-propagating right shift. Divides by2^n, rounding toward negative infinity for negatives.>>>— zero-fill right shift. Treats the value as an unsigned 32-bit int. Only operator that genuinely differs from regular arithmetic on negatives.
Real uses #1: bit flags
When you have a small fixed set of yes/no options, packing them into a single integer saves space and makes "set / unset / test" operations one CPU instruction.
const READ = 1 << 0; // 0b001 = 1 const WRITE = 1 << 1; // 0b010 = 2 const EXEC = 1 << 2; // 0b100 = 4 // Combine flags let permissions = READ | WRITE; // 0b011 = 3 // Test a flag const canWrite = (permissions & WRITE) !== 0; console.log(canWrite); // true // Add a flag permissions |= EXEC; // 0b111 = 7 // Remove a flag permissions &= ~WRITE; // 0b101 = 5 // Toggle a flag permissions ^= READ; // 0b100 = 4 console.log(permissions); // 4
true 4
Node's fs.constants, web event listener options, and many native APIs still expose flags this way. Recognising the pattern saves you a trip to the documentation.
Real uses #2: integer parsing and truncation
// Truncate toward zero — no library call needed 3.9 | 0; // 3 -3.9 | 0; // -3 Math.trunc(3.9); // 3 — clearer, same result // "Is x in range 0..N?" without two comparisons // (x >>> 0) < N === (x >= 0 && x < N), as long as N <= 2^31 const inRange = (i >>> 0) < arr.length;
Real uses #3: low-level interop
When you parse a binary protocol, manipulate canvas pixels, or read from a Uint8Array, bitwise operators are unavoidable.
Pack RGBA into one integer
function rgba(r, g, b, a) {
return ((a & 0xff) << 24) |
((b & 0xff) << 16) |
((g & 0xff) << 8) |
(r & 0xff);
}
function unpack(p) {
return {
r: p & 0xff,
g: (p >> 8) & 0xff,
b: (p >> 16) & 0xff,
a: (p >>> 24) & 0xff, // unsigned shift to avoid sign issues
};
}
const pixel = rgba(255, 128, 64, 255);
console.log(pixel.toString(16)); // "ff4080ff"
console.log(unpack(pixel)); // { r: 255, g: 128, b: 64, a: 255 }BigInt has its own bitwise operators
For values outside the 32-bit range, the same operator symbols work on BigInt. They behave like infinitely-wide signed integers (no overflow).
(1n << 40n); // 1099511627776n (1n << 40n) & 0xffn; // 0n ~0n; // -1n
When NOT to reach for bitwise
For booleans — use
&&,||,!. Bitwise&and|skip short-circuiting, which costs you the safety of guarded expressions.For modular arithmetic on non-power-of-two moduli —
%is what you want.For division by arbitrary numbers —
>>only works for powers of two.For very large numbers — convert to
BigIntfirst.For readability —
Math.trunc,Math.floor, and named flags read better than a wall of&,|,<<.