Complete JavaScript Course Module 14: Advanced: Advanced Language Features

JavaScript Bitwise Operators

  • Beginner
  • Lesson 14.5
  • 6 min read
  • 65 of 76 in the course

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.

BinaryCalculationDecimal
000111
00112 + 13
01014 + 15
01104 + 26
100088
11118 + 4 + 2 + 115

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).

OperatorNameWhat it doesExampleResult
&AND1 only if both bits are 15 & 3 (0101 & 0011)1 (0001)
|OR1 if either bit is 15 | 37 (0111)
^XOR1 if the bits are different5 ^ 36 (0110)
~NOTFlips every bit~5-6
<<Left shiftMoves bits left, adds 0s on the right5 << 110 (1010)
>>Right shiftMoves bits right, keeps the sign5 >> 12 (0010)
>>>Unsigned right shiftMoves bits right, fills with 0s-5 >>> 04294967291
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 for true/false conditions. & and | work on bits and return numbers. if (a & b) is almost always a typo for if (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 | 0 gives 7.
  • Numbers larger than about 2.1 billion break: 2147483648 | 0 gives -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.
  • & ~flag turns 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() or Math.floor() instead of x | 0 or ~~x.
  • Use n % 2 === 0 for 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 READ or WRITE. Nobody remembers what perms & 4 means 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 % 2 unless you are really working with bits.