Bitwise operators work on the individual binary digits (bits) of a number: the 1s and 0s underneath. You will not use them every day, but they show up in graphics, games, permission systems, and anywhere many yes/no settings are packed into one number. They are also a great way to understand how computers really store numbers.
Every whole number can be written in binary. Each position is worth double the one to its right:
// 8 4 2 1
// 0 1 0 1 = 4 + 1 = 5
// 1 1 0 1 = 8 + 4 + 1 = 13
console.log((13).toString(2)); // decimal to binary
console.log(0b1101); // binary to decimal
Numbers with only one bit set are the powers of two: 1, 2, 4, 8, 16, 32, 64... Setting more bits adds them up: 101101 is 32 + 8 + 4 + 1 = 45.
JavaScript normally stores numbers as 64-bit floating point. But bitwise operators work on 32-bit whole numbers. Before each bitwise operation, JavaScript converts the number to a 32-bit signed integer, and afterward converts the result back. Two consequences:
5.9 | 0 gives 5| Op | Name | What happens | Example |
|---|---|---|---|
| & | AND | 1 where both bits are 1 | 5 & 1 = 1 |
| | | OR | 1 where either bit is 1 | 5 | 1 = 5 |
| ^ | XOR | 1 where the bits differ | 5 ^ 1 = 4 |
| ~ | NOT | flips every bit | ~5 = -6 |
| << | Zero fill left shift | bits move left, zeros come in on the right | 5 << 1 = 10 |
| >> | Signed right shift | bits move right, copies of the sign bit come in | 5 >> 1 = 2 |
| >>> | Zero fill right shift | bits move right, zeros come in on the left | 5 >>> 1 = 2 |
Line the two numbers up bit by bit, and apply the rule to each column:
// 5 = 0101
// 3 = 0011
// ------------
// 5 & 3 = 0001 = 1 (only where both are 1)
// 5 | 3 = 0111 = 7 (where either is 1)
// 5 ^ 3 = 0110 = 6 (where they differ)
console.log(5 & 3);
console.log(5 | 3);
console.log(5 ^ 3);
&& and || work with true/false; single & and | work on bits. 5 && 3 is 3, but 5 & 3 is 1. Typing one & in an if condition by accident often still "works" on true/false values, hiding the mistake until it doesn't.
console.log(~5);
console.log(~0);
Why is ~5 equal to -6 and not 10? With only 4 bits, flipping 0101 would give 1010, which is 10. But JavaScript flips all 32 bits, including the sign bit:
// 5 = 00000000000000000000000000000101
// ~5 = 11111111111111111111111111111010 (sign bit is now 1: negative)
JavaScript stores negative numbers in a format called two's complement: a negative number is the bitwise NOT of the positive number, plus 1. So -5 is ~5 + 1, and flipping the bits of any n always gives -(n + 1):
| 00000000000000000000000000000110 | 6 |
| 11111111111111111111111111111010 | -6 |
| 00000000000000000000000000101000 | 40 |
| 11111111111111111111111111011000 | -40 |
Shifting left by 1 doubles a whole number; shifting right by 1 halves it (dropping any remainder):
console.log(5 << 1); // 0101 becomes 1010
console.log(5 << 3); // 5 × 2 × 2 × 2
console.log(20 >> 2); // 20 ÷ 2 ÷ 2
console.log(7 >> 1); // remainder dropped
The two right shifts only differ for negative numbers. >> copies the sign bit in from the left, so the number stays negative. >>> pushes in zeros, so the sign bit becomes 0 and the result turns into a huge positive number:
console.log(-5 >> 1); // sign kept
console.log(-5 >>> 1); // sign bit replaced by 0
Like +=, each bitwise operator has an assignment version that does the operation and stores the result:
| Operator | Same as | With x = 10 |
|---|---|---|
| x &= 5 | x = x & 5 | 0 |
| x |= 5 | x = x | 5 | 15 |
| x ^= 5 | x = x ^ 5 | 15 |
| x <<= 2 | x = x << 2 | 40 |
| x >>= 1 | x = x >> 1 | 5 |
| x >>>= 1 | x = x >>> 1 | 5 |
10 is 1010 and 5 is 0101: they share no bits, which is why & gives 0 and both | and ^ give 1111 (15).
Each bit can stand for one yes/no setting. Many settings then fit in a single number:
const READ = 1; // 001
const WRITE = 2; // 010
const DELETE = 4; // 100
let perms = READ | WRITE; // turn on read and write: 011 = 3
console.log((perms & WRITE) !== 0); // check: has write?
console.log((perms & DELETE) !== 0); // check: has delete?
perms |= DELETE; // turn delete on: 111 = 7
perms &= ~WRITE; // turn write off: 101 = 5
console.log(perms.toString(2));
| turns a flag on, & checks it, & ~ turns it off, and ^ toggles it. That handful of moves covers nearly every real use.
// Odd or even: the last bit of every odd number is 1
console.log(7 & 1); // odd
console.log(8 & 1); // even
// Decimal to binary, including negatives
console.log((-5 >>> 0).toString(2));
// Binary string to decimal
console.log(parseInt("1101", 2));
(-5).toString(2) would give "-101", a minus sign in front of 5's bits. Shifting by zero with >>> 0 first turns the number into its raw 32-bit pattern, so you see what is really stored.
What is 12 & 10? (12 is 1100, 10 is 1010.)
8146221000 is 8. (14 is 12 | 10, and 6 is 12 ^ 10.)What is 3 << 4?
48. Each left shift doubles: 3 → 6 → 12 → 24 → 48. It is the same as 3 × 24, or 3 × 16.With the permission flags above, which line turns WRITE off without touching the others?
perms |= WRITE;perms &= ~WRITE;perms = WRITE;perms &&= WRITE;~WRITE is all 1s except the write bit, so AND-ing with it clears just that bit. A turns write on, C wipes out every other flag, and D is a logical operator, not a bitwise one.12 & 10, 12 | 10, and 12 ^ 10 on paper, then log them to check<< to compute 3 times 16 without using *^=, logging perms.toString(2) each time-8 >> 1 and -8 >>> 1. Explain the difference