Bitwise Operations in JavaScript: A Practical Guide
Bitwise operators work directly on the binary representation of numbers. JavaScript converts numbers to 32-bit signed integers for bitwise operations, applies the operation bit-by-bit, then converts the result back to a JavaScript number. They're faster than their mathematical equivalents for certain tasks and essential for working with packed data, hardware interfaces, and graphics.
Visualise binary conversions: Our Number Base Converter shows decimal, hex, binary, and octal side-by-side so you can see exactly what the bits look like.
Open Number Base Converter โTable of Contents
The Six Bitwise Operators
| Operator | Symbol | Effect |
|---|---|---|
| AND | & | 1 only if both bits are 1 |
| OR | | | 1 if either bit is 1 |
| XOR | ^ | 1 if bits are different |
| NOT | ~ | Flips all bits |
| Left shift | << | Shifts bits left, fills with 0 |
| Right shift | >> | Shifts bits right, preserves sign |
AND: Masking and Testing Flags
// Test if a bit is set
const value = 0b1010; // 10 in decimal
value & 0b0010; // 0b0010 = 2 โ the bit IS set
value & 0b0100; // 0b0000 = 0 โ the bit is NOT set
// Practical: check if a number is even or odd
// Even numbers have 0 in the lowest bit; odd numbers have 1
const isOdd = n => (n & 1) === 1;
const isEven = n => (n & 1) === 0;
// Extract the lower byte of a 32-bit integer
const lower8bits = value & 0xFF;
OR: Setting Flags
// Permission flags
const READ = 1; // 0b001
const WRITE = 2; // 0b010
const EXECUTE = 4; // 0b100
// Grant read and write permission
let perms = READ | WRITE; // 0b011 = 3
// Grant execute permission to existing perms
perms = perms | EXECUTE; // 0b111 = 7 (rwx)
XOR: Toggling and Swapping
// Toggle a bit
let flags = 0b1010;
flags ^= 0b0010; // toggle bit 1 โ 0b1000
flags ^= 0b0010; // toggle again โ 0b1010 (back to original)
// XOR swap (no temp variable needed)
let a = 5, b = 3;
a ^= b; // a = 6 (5 XOR 3)
b ^= a; // b = 5 (3 XOR 6)
a ^= b; // a = 3 (6 XOR 5)
// a=3, b=5 โ swapped!
// Simple (not cryptographic) hash/checksum
function xorChecksum(bytes) {
return bytes.reduce((acc, byte) => acc ^ byte, 0);
}
NOT: Inverting Bits
// ~ inverts all 32 bits and the result for positive n is -(n+1)
~5 // -6
~0 // -1
~-1 // 0
// Practical: ~~ as a fast Math.floor for positive numbers
~~3.7 // 3
~~-3.7 // -3 (careful โ differs from Math.floor for negatives)
// Remove a permission flag
let perms = 0b111; // rwx
const WRITE = 0b010;
perms &= ~WRITE; // perms = 0b101 โ write bit cleared
Shift Operators
// Left shift: multiply by 2 per shift
1 << 0 // 1
1 << 1 // 2
1 << 2 // 4
1 << 8 // 256
1 << 10 // 1024
// Right shift: divide by 2 per shift (integer division)
256 >> 1 // 128
256 >> 4 // 16
// Practical: create a bit mask for position n
const maskForBit = n => 1 << n;
maskForBit(3) // 0b1000 = 8
// Extract a byte from a 32-bit integer
const getbyte = (n, pos) => (n >> (pos * 8)) & 0xFF;
getbyte(0xFF5733, 1) // 0x57 = 87 (green channel)
Real-World: RGB Color Manipulation
// Pack RGB into single integer
function rgbToInt(r, g, b) {
return (r << 16) | (g << 8) | b;
}
// Unpack RGB from single integer
function intToRgb(color) {
return {
r: (color >> 16) & 0xFF,
g: (color >> 8) & 0xFF,
b: color & 0xFF
};
}
// Darken a color by 50%
function darken(color) {
const { r, g, b } = intToRgb(color);
return rgbToInt(r >> 1, g >> 1, b >> 1);
}
// Blend two colors at 50/50
function blend(c1, c2) {
const a = intToRgb(c1), b = intToRgb(c2);
return rgbToInt(
(a.r + b.r) >> 1,
(a.g + b.g) >> 1,
(a.b + b.b) >> 1
);
}
Gotchas in JavaScript
JavaScript converts to 32-bit signed integer before bitwise operations. Numbers larger than 2,147,483,647 will produce unexpected results. For large integers use BigInt: BigInt(value) & BigInt(mask).
The unsigned right shift operator >>> is unique to JavaScript โ it fills with zeros regardless of sign, treating the number as unsigned. Use it when you want the result to always be a non-negative integer: -1 >>> 0 gives 4294967295.
Operator precedence. Bitwise operators have lower precedence than comparison operators. Always use parentheses: (n & MASK) !== 0, not n & MASK !== 0 (which evaluates as n & (MASK !== 0)).
