Bitwise operators in JavaScript are fundamental to the language. These operators apply logical operations to each bit of the operands. They consist of bitwise NOT (~), bitwise AND (&), bitwise OR (|), and bitwise XOR (^), as well as their assignment variations &=, |=, and ^=. This post discusses what they are and the best practices around them.

The shift operators, <<, >>, and >>> as well as their assignment variations, are out of the scope of this post.

What Are Bitwise Operators?

How Are Integers Represented in Memory?

Integers in memory are represented by a certain number of bits. In JavaScript, integers, including BigInt, are represented using the method of two’s complement. For example, using 32 bits,

  • 20 is represented as 0000 0000 0000 0000 0000 0000 0001 0100, and
  • -20 is represented as 1111 1111 1111 1111 1111 1111 1110 1100.

What Do Bitwise Operators Do?

Bitwise operators apply logical operations to each bit of the binary representation of integers. Assuming that the operands are 32-bit integers:

  • Bitwise NOT ~: Apply the logical operation NOT to each bit of an integer, i.e., flip every bit of an integer. For example, ~20 is -21, whose binary form is 1111 1111 1111 1111 1111 1111 1110 1011.
  • Bitwise AND &: Apply the logical operation AND to each pair of bits of two integers. For example, 20 & (-20) is 4, whose binary form is 0000 0000 0000 0000 0000 0000 0000 0100.
  • Bitwise OR |: Apply the logical operation OR to each pair of bits of two integers. For example, 20 | (-20) is -4, whose binary form is 1111 1111 1111 1111 1111 1111 1111 1100.
  • Bitwise XOR ^: Apply the logical operation XOR to each pair of bits of two integers. For example, 20 ^ (-20) is -8, whose binary form is 1111 1111 1111 1111 1111 1111 1111 1000.

The table below summarizes the binary bitwise operators:

OperatorOperands/Result
DecimalBinary
Operand200000 0000 0000 0000 0000 0000 0001 0100
Operand-201111 1111 1111 1111 1111 1111 1110 1100
&40000 0000 0000 0000 0000 0000 0000 0100
|-41111 1111 1111 1111 1111 1111 1111 1100
^-81111 1111 1111 1111 1111 1111 1111 1000

What Data Types Do JavaScript Bitwise Operators Work With?

In JavaScript, bitwise operators only apply to 32-bit integers and BigInt. Any operands with another type would be first cast to either of these.

For example, consider the expression ~1.1. Instead of applying the bitwise NOT operator on the binary representation of 1.1 (which is possible in other contexts), a JavaScript engine first casts 1.1 to 1, and then applies the bitwise NOT operator to 1 as a 32-bit integer, which results in -2.

For another example, consider the expression ~0xaa11223344. 0xaa11223344 has exceeded the maximum of 32-bit representation. In this case, a JavaScript engine calculates 0xaa11223344 modulo 232, which results in 0x11223344, and then applies the bitwise NOT operator to it. The result is -287454021. In fact, we can verify that ~0x11223344, ~0xa11223344, ~0xaa11223344, and ~0xaaa11223344 all result in the same number.

How Are Bitwise Operators Used?

Bitwise operators are used for:

  1. enabling advanced or specialized algorithms, such as those in cryptography, and
  2. facilitating integers representing multiple Boolean variables, i.e., bitmasking.

Details of bitwise operators in advanced or specialized algorithms are out of the scope of this post. They are more suitable to be discussed in the context of those algorithms. This post focuses on bitmasking.

Traditional Uses in C

While bitwise operators are not prevalently directly used in most JavaScript programs, they are often heavily used in a traditional programming language, such as C. The basic idea is that, instead of using multiple Boolean variables to represent a group of on/off statuses/options, it is more efficient to use bits in an integer. In this way, a 32-bit integer can represent up to 32 on/off statuses/options. This method is also referred to as bitmasking.

Example

For example, the POSIX C function open opens a file. It takes an integer parameter oflags, which indicates the options that the caller wants to use, e.g.,

open("/path/to/file", O_WRONLY | O_CREAT);

Here, O_WRONLY and O_CREAT are two options, meaning write-only and creating the file if it doesn’t exist, respectively. A possible implementation of open defines O_WRONLY as 1 (binary 01) and O_CREAT as 2 (binary 10). In this way, the caller can choose to turn on:

  • both options (O_WRONLY | O_CREAT) or,
  • only one of them (O_WRONLY or O_CREAT), or
  • neither (0).

Effectively, the lowest 2 bits of oflags are used as two Boolean variables, representing the O_WRONLY and O_CREAT options, respectively.

Bitwise operators also help the caller manipulate oflags:

  • oflags |= O_WRONLY turns on write-only,
  • oflags &= ~O_WRONLY turns off write-only, and
  • oflags ^ OWRONLY flips write-only.

Inside the function body of open, the presences of these two options can be retrieved via determining whether oflags & O_WRONLY and oflags & O_CREAT are not zero.

Uses in JavaScript

In JavaScript, bitwise operators are used much less commonly than they are in C. Still, there are some occasions that they are used in this way.

One notable example is Node.compareDocumentPosition. Its return value is a bitmask that requires bitwise operators to retrieve the results. For example, consider the following HTML document:

<!doctype html>
<html lang="en">
  <body>
    <p>This is a <em>paragraph</em>.</p>
  </body>
</html>

You can open this HTML document, then run the following in the JavaScript console:

p = document.getElementsByTagName("p")[0];
em = document.getElementsByTagName("em")[0];
const relPosition = p.compareDocumentPosition(em);
console.log(relPosition);

The console prints 20. This magic number means nothing until we unmask them using bitwise operators:

console.log(`Does p contains em? ${(relPosition & Node.DOCUMENT_POSITION_CONTAINED_BY) !== 0}`); // true
console.log(`Is em after p? ${(relPosition & Node.DOCUMENT_POSITION_FOLLOWING) !== 0}`); // true
console.log(`Does em contains p? ${(relPosition & Node.DOCUMENT_POSITION_CONTAINING) !== 0}`); // false

When Should I Use Bitwise Operators?

As discussed in the How Are Bitwise Operators Used?, there are two main use cases of bitwise operators: advanced/specialized algorithms and bitmasking.

If we are implementing an advanced or specialized algorithm that requires bitwise operators, then the answer is clearly yes. But what about bitmasking? We need to first understand the most common and idiomatic alternative to bitmasking in JavaScript.

Representing a Group of Boolean Variables in JavaScript

In JavaScript, the most idiomatic way to represent a group of Boolean variables is using an object with string keys and Boolean values. The C example above, if reimplemented in JavaScript, its option oflags would be an object. Borrowing the TypeScript syntax, its type looks like:

interface OflagsType {
  append: boolean;
  creat: boolean;
  // ...
}

The JavaScript Node.compareDocumentPosition example, if reimplemented, should return an object of type:

interface DocumentPosition {
  documentPositionContainedBy: boolean;
  documentPositionFollowing: boolean;
  documentPositionContaining: boolean;
  // ...
}

Pros and Cons of Bitmasking

The table compares bitmasking to representing a group of Boolean variables using an object:

BitmaskingObject
Intuitive
Less error-prone
Easy to use
Flexible with non-Boolean
Efficient
Compatible--

Based on this table, we should always prefer using an object unless:

  • The situation demands the use of bitmasking for compatibility reasons, such as interacting with Node.compareDocumentPosition, or
  • performance gain of using bitmasking is important to the program. Keep in mind that this performance gain is tiny for each occurrence, but there still can be situations in which the performance gain is important, .e.g, when the bitmask is read or written many times in a short period of time.

Therefore, we should normally use an object unless a special circumstance commands bitmasking.

A Decision Flow Chart

Decision Flow Chart on Using an Object or Bitmasking

Conclusion

  • Bitwise operators apply to each bit of operands in their binary form.
  • Bitwise operators are used for:
    • enabling advanced or special algorithms, and
    • facilitating integers representing multiple Boolean variables, i.e., bitmasking.
  • In JavaScript, we usually prefer objects consisting of Boolean values over bitmasking unless a special circumstance justifies otherwise.