Hidden Power of Bitwise Operators

Don't run away when you see a bitwise operator. Someday one would save your day.

Hidden Power of Bitwise Operators
Photo by Alexandre Debiève / Unsplash

Bitwise Operators are not very common to see in our daily codebases. Even it's sometime considered as bad-practice (because of reducing code readability). But still, in 2025, bitwise operators shine in some use-cases, especially the ones the performance matters a lot.

Bitwise operators are often underutilized, but they can be very efficient for certain tasks. Here are some common use cases where bitwise operators can help in everyday coding tasks:

1. Checking if a number is even or odd

You can check if a number is even or odd by using the & (bitwise AND) operator. If the least significant bit (LSB) is 1, the number is odd; if it's 0, the number is even.

Example:

function isOdd(n: number): boolean {
    return (n & 1) === 1;
}

Checking if a number is even or odd

2. Swapping two numbers without using a temporary variable

You can use the XOR (^) operator to swap two numbers without a temporary variable.

Example:

let a = 5, b = 3;

a = a ^ b;
b = a ^ b; // b becomes 5
a = a ^ b; // a becomes 3

// Now, a = 3 and b = 5

Swapping two numbers without using a temporary variable

3. Clearing a specific bit

To clear a specific bit (set it to 0) in a number, you can use the bitwise & operator combined with a mask.

Example:

let num = 14; // Binary: 1110
let mask = ~(1 << 2); // Binary: 1011 (2nd bit is 0)
num = num & mask; // Binary: 1010 (clears 2nd bit)

Clear the 2nd bit (0-indexed) of a number

4. Setting a specific bit

To set a specific bit (make it 1), you can use the bitwise | operator.

Example:

let num = 10; // Binary: 1010
let mask = 1 << 1; // Binary: 0010
num = num | mask; // Binary: 1010 (1st bit is already set)

Set the 1st bit (0-indexed) of a number

5. Flipping (toggling) a specific bit

To toggle a specific bit, use the XOR (^) operator.

Example:

let num = 10; // Binary: 1010
let mask = 1 << 1; // Binary: 0010
num = num ^ mask; // Binary: 1000 (1st bit toggled)

Toggle the 1st bit (0-indexed) of a number

6. Counting the number of set bits (Hamming Weight)

You can count the number of 1s in the binary representation of a number using bitwise operators.

Example:

function countSetBits(n: number): number {
    let count = 0;
    while (n) {
        count += n & 1; // Increment count if the least significant bit is 1
        n >>= 1; // Shift the number to the right
    }
    return count;
}

Counting the number of set bits

7. Checking if a number is a power of 2

A number is a power of 2 if it has exactly one bit set to 1 in its binary representation. This can be checked using the expression n & (n - 1) == 0.

Example:

function isPowerOfTwo(n: number): boolean {
    return n > 0 && (n & (n - 1)) === 0;
}

Checking if a number is a power of 2

8. Finding the minimum or maximum of two numbers

You can find the minimum or maximum of two numbers without using conditional statements by using bitwise operators.

Example:

function min(a: number, b: number): number {
    return b ^ ((a ^ b) & -(a < b));
}

Finding the minimum of two numbers

9. Quickly multiplying or dividing by powers of 2

Bitwise shifts (<< for left shift and >> for right shift) are efficient ways to multiply or divide numbers by powers of 2.

Example:

let num = 5;
let result = num << 2; // Equivalent to 5 * 4 = 20

Multiply by 2^2 with bitwise shift

10. Finding the only non-repeating element in an array

As explained earlier, you can use XOR to find the element that appears only once in an array where all other elements appear twice.

Example:

function findUnique(arr: number[]): number {
    let result = 0;
    for (const num of arr) {
        result ^= num;
    }
    return result;
}

Finding the only non-repeating element in an arra

11. Getting the absolute value of a number

You can compute the absolute value of a number using bitwise operations. This involves finding the sign bit and conditionally inverting the number.

Example:

function absoluteValue(n: number): number {
    const mask = n >> 31; // Get the sign bit (1 for negative, 0 for positive)
    return (n + mask) ^ mask; // If negative, flip bits and add 1
}

Getting the absolute value of a number

12. Checking if two integers have opposite signs

You can check if two integers have opposite signs using XOR. If the result of XOR is negative, the numbers have opposite signs.

Example:

function haveOppositeSigns(a: number, b: number): boolean {
    return (a ^ b) < 0;
}

Checking if two integers have opposite signs

13. Reversing the bits of a number

Bitwise operations can be used to reverse the bits in a number's binary representation.

Example:

function reverseBits(n: number): number {
    let result = 0;
    while (n > 0) {
        result = (result << 1) | (n & 1); // Shift result left and add LSB of n
        n >>= 1; // Shift n right
    }
    return result;
}

Reversing the bits of a number

These are just some practical examples where bitwise operations can be highly efficient, especially in cases involving low-level optimizations, bit manipulations, or resource-constrained environments like embedded systems.

Use-cases other than numbers

Bitwise operators are typically used for manipulating numbers, but they can also be applied in creative ways for other data types or problems where data can be represented in binary form. Here are a few non-number-specific use cases where bitwise operators can be useful:

1. Boolean Arrays (Bitmasks)

Instead of using an array of booleans (true/false), you can use a single integer to represent the state of multiple booleans. This approach saves space and can be manipulated with bitwise operators. (Check Bonus section)

Example: Storing the status of 5 switches (on/off)

You can represent the on/off status of 5 switches as a bitmask where each bit represents a switch:

  • 1 = on, 0 = off.
let switches = 0; // All switches off (00000)

// Turn on the first and third switches
switches |= (1 << 0); // Turn on the first switch (00001)
switches |= (1 << 2); // Turn on the third switch (00101)

// Check if the second switch is on
const isSecondSwitchOn = (switches & (1 << 1)) !== 0; // false

// Turn off the third switch
switches &= ~(1 << 2); // (00001)

Storing the status of 5 switches (on/off)

This can be useful in games, permission systems, or toggling states of features.

2. Set Operations Using Bitwise Operators

You can use bitwise operators to simulate basic set operations (like union, intersection, and difference) when working with sets that can be mapped to integers. Each bit in a number can represent whether an element is present (1) or absent (0).

Example: Set operations

Consider sets represented as bitmasks, where each bit corresponds to an element.

  • Set A: 0101 (elements 1 and 3 are in the set)
  • Set B: 0011 (elements 1 and 2 are in the set)
  • Union (A | B): 0111 (elements 1, 2, and 3 are in the union)
  • Intersection (A & B): 0001 (element 1 is in the intersection)
  • Difference (A & ~B): 0100 (element 3 is in the difference)

This can be useful in areas like:

  • Feature flags: Enabling or disabling features in software.
  • Role-based access control: Managing user permissions efficiently.

3. Character Manipulation

You can use bitwise operators to work with characters, particularly when manipulating or comparing ASCII values. Since characters are represented by integers (ASCII or Unicode), bitwise operations can be applied.

Example: Convert a lowercase letter to uppercase

In ASCII, lowercase letters and uppercase letters differ by a single bit (the 6th bit). You can toggle this bit to convert between cases.

function toUpperCase(char: string): string {
    return String.fromCharCode(char.charCodeAt(0) & ~32);
}

console.log(toUpperCase('a')); // Output: 'A'

Here, & ~32 clears the 6th bit to convert any lowercase letter to uppercase.

4. Bitmap Image Processing

In image processing, pixels are often represented in binary, especially for monochrome (black and white) images where each pixel is a bit (1 for black, 0 for white). Bitwise operations are useful for manipulating such images.

Example: Inverting colors in a bitmap

If you have an image where each pixel is represented as a single bit (1 for black, 0 for white), you can invert the colors by flipping the bits.

function invertBitmap(bitmap: number): number {
    return ~bitmap;
}

Inverting colors in a bitmap

This would invert all bits in the binary representation of the image.

5. Efficient Permission Systems

Permissions in software (such as read, write, execute) can be efficiently stored as bits. Each permission can be represented as a bit in an integer, and you can use bitwise operators to check, grant, or revoke permissions.

Example: Permission flags

Imagine you have permission flags for a file:

  • Read: 1 << 0 (0001)
  • Write: 1 << 1 (0010)
  • Execute: 1 << 2 (0100)

You can store permissions as an integer, and then use bitwise operators to check or modify the permissions.

let permissions = 0; // No permissions

// Grant read and write permissions
permissions |= (1 << 0); // Read (0001)
permissions |= (1 << 1); // Write (0011)

// Check if execute permission is granted
const canExecute = (permissions & (1 << 2)) !== 0; // false

Permission flags

6. Graph Algorithms (Subset Manipulation)

In combinatorial problems, particularly those involving subsets, you can use bitwise operators to efficiently represent and manipulate subsets.

Example: Enumerating all subsets of a set

If you have a set of N elements, you can represent each subset as an N-bit integer, where each bit indicates whether a corresponding element is included in the subset.

function generateSubsets(arr: number[]): number[][] {
    const subsets = [];
    const n = arr.length;
    const totalSubsets = 1 << n; // 2^n subsets

    for (let i = 0; i < totalSubsets; i++) {
        const subset = [];
        for (let j = 0; j < n; j++) {
            if (i & (1 << j)) {
                subset.push(arr[j]);
            }
        }
        subsets.push(subset);
    }
    return subsets;
}

console.log(generateSubsets([1, 2, 3]));
// Output: [[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]

Enumerating all subsets of a set

7. Handling Multiple Boolean Flags

If you need to handle multiple boolean flags or options in a single variable, you can use bitwise operators to store them compactly in a single integer.

Example: Storing multiple feature flags

const FLAG_A = 1 << 0; // 0001
const FLAG_B = 1 << 1; // 0010
const FLAG_C = 1 << 2; // 0100

let options = 0;

// Enable FLAG_A and FLAG_C
options |= FLAG_A;
options |= FLAG_C;

// Check if FLAG_B is enabled
const isFlagBEnabled = (options & FLAG_B) !== 0; // false

Storing multiple feature flags

8. Compression Algorithms

Bitwise operations are heavily used in compression algorithms to encode and decode data efficiently. Huffman coding, for example, uses bits to represent variable-length codes for characters.

9. Cryptography

Bitwise operators are used extensively in cryptographic algorithms (e.g., AES, DES, SHA). They allow efficient manipulation of binary data, which is fundamental in encrypting and decrypting data.

10. Finite State Machines

Bitwise operations can be used to represent and manipulate states in a finite state machine. Each bit can represent a different state, allowing you to perform state transitions efficiently using bitwise operators.

Bonus: Simple authorization with Bitwise Operators

Bitwise operators are commonly used to implement simple authorization checks by using bitmasks to represent different permissions or roles. This technique is highly efficient because it allows multiple permissions to be stored in a single integer, and operations to check, grant, or revoke permissions can be performed using bitwise logic.

Use Case: Authorization with Bitwise Operators

Let's say you have a system where different permissions are represented by individual bits in a binary number. Each bit represents a particular permission, and you can combine multiple permissions into a single number.

Example Permissions:

  • Read: 1 << 0 (Binary: 0001, Decimal: 1)
  • Write: 1 << 1 (Binary: 0010, Decimal: 2)
  • Execute: 1 << 2 (Binary: 0100, Decimal: 4)
  • Delete: 1 << 3 (Binary: 1000, Decimal: 8)

A user can have any combination of these permissions by using a bitmask. For example:

  • If a user has read and execute permissions, their permission bitmask would be: 0001 | 0100 = 0101 (Decimal: 5).

Common Operations:

  1. Granting Permissions: Use the bitwise OR (|) operator to add a permission.
  2. Revoking Permissions: Use the bitwise AND NOT (& ~) operator to remove a permission.
  3. Checking Permissions: Use the bitwise AND (&) operator to verify if a specific permission is granted.

Example Code:

// Permission flags
const READ = 1 << 0;  // 0001
const WRITE = 1 << 1; // 0010
const EXECUTE = 1 << 2; // 0100
const DELETE = 1 << 3; // 1000

// Current user's permissions (initially no permissions)
let userPermissions = 0;

// Grant the user read and write permissions
userPermissions |= READ;  // 0001
userPermissions |= WRITE; // 0011 (both read and write now)

// Check if the user has execute permission
const canExecute = (userPermissions & EXECUTE) !== 0; // false

// Grant execute permission
userPermissions |= EXECUTE; // 0111 (read, write, and execute now)

// Revoke write permission
userPermissions &= ~WRITE; // 0101 (read and execute, but no write)

// Check if the user has write permission
const canWrite = (userPermissions & WRITE) !== 0; // false

Explanation of Operations:

  1. Granting Permissions:
    • userPermissions |= READ: This sets the read bit to 1.
    • userPermissions |= EXECUTE: This sets the execute bit to 1 without affecting other permissions.
  2. Revoking Permissions:
    • userPermissions &= ~WRITE: This clears the write bit by negating the mask (~WRITE is 1101), then using AND to clear the write bit without touching other bits.
  3. Checking Permissions:
    • userPermissions & EXECUTE: This checks if the execute bit is set (1). If it is, the result will be non-zero; otherwise, it will be 0.

Use Case: Role-Based Access Control (RBAC)

You can also use bitwise operations to handle user roles in a system. For instance, different roles (e.g., Admin, Editor, Viewer) can be represented as bits, and users can be assigned multiple roles using a single integer.

Example Roles:

  • Admin: 1 << 0 (Binary: 0001, Decimal: 1)
  • Editor: 1 << 1 (Binary: 0010, Decimal: 2)
  • Viewer: 1 << 2 (Binary: 0100, Decimal: 4)

You can combine roles and check them easily:

const ADMIN = 1 << 0;
const EDITOR = 1 << 1;
const VIEWER = 1 << 2;

// Assign a user both Admin and Editor roles
let userRoles = ADMIN | EDITOR; // Binary: 0011, Decimal: 3

// Check if the user is an Admin
const isAdmin = (userRoles & ADMIN) !== 0; // true

// Check if the user is a Viewer
const isViewer = (userRoles & VIEWER) !== 0; // false

// Grant Viewer role to the user
userRoles |= VIEWER; // Binary: 0111 (Admin, Editor, and Viewer)

// Revoke Editor role
userRoles &= ~EDITOR; // Binary: 0101 (Admin and Viewer, no Editor)

Advantages of Using Bitwise Operators for Authorization:

  1. Space Efficiency: You can store multiple permissions or roles in a single integer, saving space compared to arrays or objects.
  2. Performance: Bitwise operations are extremely fast, often faster than other logical checks like multiple if-else conditions.
  3. Scalability: You can easily add more permissions by adding more bits. For example, the 32-bit or 64-bit integer types allow you to store up to 32 or 64 permissions or roles.

Conclusion

Bitwise operations can be applied to a wide range of data types and problems beyond just numbers, especially when data can be represented in binary form (like sets, flags, or booleans). They provide an efficient and compact way to perform operations on data that would otherwise require more space or complex logic.

This article is written with the help of ChatGPT.
Me on Mastodon: https://synaps.space/@murat