In short
JavaScript bitwise operators work on the single bits of a number. Learn binary numbers, the & | ^ ~ << >> >>> operators, and practical uses like permission flags. Binary numbers in simple words We normally count in decimal, with ten digits from 0 to 9.
JavaScript bitwise operators work on the single bits of a number. Learn binary numbers, the & | ^ ~ << >> >>> operators, and practical uses like permission flags.
Binary numbers in simple words
We normally count in decimal, with ten digits from 0 to 9. Computers store everything in binary, with only two digits: 0 and 1. Each digit is called a bit. Think of bits as a row of light switches: each one is either off (0) or on (1).
In decimal, each place is worth 10 times more than the one to its right (1, 10, 100...). In binary, each place is worth 2 times more (1, 2, 4, 8, 16...). So binary 101 means one 4, zero 2s and one 1, which is 4 + 0 + 1 = 5.
| Binary | Calculation | Decimal |
|---|---|---|
0001 | 1 | 1 |
0011 | 2 + 1 | 3 |
0101 | 4 + 1 | 5 |
0110 | 4 + 2 | 6 |
1000 | 8 | 8 |
1111 | 8 + 4 + 2 + 1 | 15 |
JavaScript can convert between them for you, and you can write binary numbers directly with the 0b prefix:
console.log((5).toString(2)); // Output: 101 (number to binary text)
console.log(parseInt("101", 2)); // Output: 5 (binary text to number)
console.log(0b1010); // Output: 10 (a binary literal)
console.log((13).toString(2).padStart(8, "0")); // Output: 00001101
toString(2) means "show this number in base 2". padStart(8, "0") adds zeros on the left so it is easier to compare bits.
The JavaScript bitwise operators: & | ^ ~ << >> >>>
A bitwise operator compares two numbers bit by bit, like comparing two rows of switches position by position. All examples below use 5 (0101) and 3 (0011).
| Operator | Name | What it does | Example | Result |
|---|---|---|---|---|
& | AND | 1 only if both bits are 1 | 5 & 3 (0101 & 0011) | 1 (0001) |
| | OR | 1 if either bit is 1 | 5 | 3 | 7 (0111) |
^ | XOR | 1 if the bits are different | 5 ^ 3 | 6 (0110) |
~ | NOT | Flips every bit | ~5 | -6 |
<< | Left shift | Moves bits left, adds 0s on the right | 5 << 1 | 10 (1010) |
>> | Right shift | Moves bits right, keeps the sign | 5 >> 1 | 2 (0010) |
>>> | Unsigned right shift | Moves bits right, fills with 0s | -5 >>> 0 | 4294967291 |
console.log(5 & 3); // Output: 1
console.log(5 | 3); // Output: 7
console.log(5 ^ 3); // Output: 6
console.log(~5); // Output: -6
console.log(5 << 1); // Output: 10 (same as 5 * 2)
console.log(5 << 3); // Output: 40 (same as 5 * 2 * 2 * 2)
console.log(20 >> 2); // Output: 5 (same as 20 / 4, rounded down)
console.log(-20 >> 2); // Output: -5 (sign is kept)
console.log(-20 >>> 2);// Output: 1073741819 (sign is lost)
Shifting left by 1 doubles the number, and shifting right by 1 halves it (rounding down). ~x always equals -(x + 1), which is why ~5 is -6.
Common mistake: Mixing up
&with&&, or|with||.&&and||are logical operators fortrue/falseconditions.&and|work on bits and return numbers.if (a & b)is almost always a typo forif (a && b).
The 32-bit note: how JavaScript stores numbers for bitwise work
Normal JavaScript numbers are 64-bit decimals. But bitwise operators first convert the number to a 32-bit whole number, do the work, and then convert back. This has three effects:
- Any decimal part is cut off:
7.9 | 0gives7. - Numbers larger than about 2.1 billion break:
2147483648 | 0gives-2147483648. - The leftmost bit is used as the sign (plus or minus), which is why
~and>>can give negative results.
console.log(7.9 | 0); // Output: 7
console.log(-7.9 | 0); // Output: -7
console.log(3000000000 | 0); // Output: -1294967296 (too big, wrapped around)
console.log(Math.trunc(3000000000.5)); // Output: 3000000000 (safe)
You may see x | 0 in old code as a "fast" way to drop decimals. Use Math.trunc(x) instead: it is clear and works for big numbers. For very large whole numbers, BigInt also supports bitwise operators, like 5n & 3n.
Practical uses: flags, permissions and even numbers
The most useful real-world job for bitwise operators is storing several yes/no settings in one number. Each setting gets its own bit, called a flag. This is how file permissions work on Linux and how many game engines and APIs store options.
// Each permission is one bit
const READ = 0b001; // 1
const WRITE = 0b010; // 2
const REMOVE = 0b100; // 4
let userPerms = READ | WRITE; // turn on READ and WRITE: 0b011 = 3
const can = (perms, flag) => (perms & flag) !== 0;
console.log(can(userPerms, READ)); // Output: true
console.log(can(userPerms, REMOVE)); // Output: false
userPerms |= REMOVE; // add a permission
console.log(userPerms.toString(2)); // Output: 111
userPerms &= ~WRITE; // remove a permission
console.log(can(userPerms, WRITE)); // Output: false
userPerms ^= READ; // toggle (flip) a permission
console.log(can(userPerms, READ)); // Output: false
|turns a flag on.&checks if a flag is on.& ~flagturns a flag off (keep everything except that bit).^toggles a flag, like flipping a light switch.
Another small trick: a number is even if its last bit is 0. So (n & 1) === 0 checks for even numbers.
const isEven = (n) => (n & 1) === 0;
console.log(isEven(10)); // Output: true
console.log(isEven(7)); // Output: false
// The clearer everyday version does the same job:
console.log(10 % 2 === 0); // Output: true
You will also meet bitwise operators when working with colors. A hex color like 0xff8800 stores red, green and blue in one number, and shifts pull them apart: (0xff8800 >> 16) & 255 gives 255, the red part.
When not to use bitwise operators
Bitwise code is short but hard to read for most developers. Use it when you really work with bits: flags, colors, binary file formats or network data. Avoid it as a "clever" shortcut:
- Use
Math.trunc()orMath.floor()instead ofx | 0or~~x. - Use
n % 2 === 0for even checks in normal code, because everyone understands it at once. - Use
array.includes(x)instead of the old~array.indexOf(x)trick.
Tip: If you do use flags, always give each bit a named constant like
READorWRITE. Nobody remembers whatperms & 4means a month later.
Practice exercise
Create flags for a text editor: BOLD = 1, ITALIC = 2, UNDERLINE = 4. Start with let style = 0. Turn on BOLD and UNDERLINE, log the style in binary, check whether ITALIC is on, then toggle BOLD off and log the final value.
Hint: use |= to turn on, & to check, ^= to toggle and toString(2) to see the bits. The final value should be 4.
Key takeaways
- Binary uses only 0 and 1; each place is worth twice the one to its right.
&,|,^and~compare or flip bits;<<,>>and>>>move them left or right.- Bitwise operators convert numbers to 32-bit whole numbers, so decimals are cut off and big numbers wrap around.
- The best everyday use is storing many on/off flags, like permissions, in a single number.
- Prefer clear code like
Math.trunc()and% 2unless you are really working with bits.