JavaScript Tutorial

JavaScript Bitwise Operators: Complete Guide with Examples

Master JavaScript bitwise operators including AND (&), OR (|), XOR (^), NOT (~), and shift operators (<<, >>, >>>). Learn binary operations, flags, permissions, and practical use cases.

Welcome back! ๐Ÿ‘‹ In the previous lessons, you learned about various operators. Now, let's explore bitwise operators - operators that work at the binary (bit) level.

Bitwise operators are more advanced and less commonly used than other operators, but they're powerful tools for specific use cases like working with permissions, flags, colors, and performance-critical operations.

Note: This is an advanced topic. If you're a beginner, you can skip this for now and return when you need to work with binary operations.


What are Bitwise Operators?

Bitwise operators treat their operands as 32-bit binary numbers (zeros and ones) and perform operations at the bit level. They then return the result as a standard JavaScript number.

JavaScript provides seven bitwise operators:

  1. & - Bitwise AND
  2. | - Bitwise OR
  3. ^ - Bitwise XOR (Exclusive OR)
  4. ~ - Bitwise NOT
  5. << - Left shift
  6. >> - Sign-propagating right shift
  7. >>> - Zero-fill right shift
// Examples of bitwise operations
console.log(5 & 3);   // 1 (Bitwise AND)
console.log(5 | 3);   // 7 (Bitwise OR)
console.log(5 ^ 3);   // 6 (Bitwise XOR)
console.log(~5);      // -6 (Bitwise NOT)
console.log(5 << 1);  // 10 (Left shift)
console.log(5 >> 1);  // 2 (Right shift)

Understanding Binary Numbers

Before diving into bitwise operators, let's understand how binary numbers work.

Decimal to Binary Conversion

// Decimal number in binary
5  โ†’ 0000 0101
3  โ†’ 0000 0011
10 โ†’ 0000 1010

// JavaScript provides methods to convert
let decimal = 5;
console.log(decimal.toString(2));  // "101" (binary representation)

// Add padding for clarity
console.log(decimal.toString(2).padStart(8, '0'));  // "00000101"

// Binary to decimal
let binary = "101";
console.log(parseInt(binary, 2));  // 5

Binary Number Positions

Each bit position represents a power of 2:

Position:  7    6    5    4    3    2    1    0
Value:    128   64   32   16    8    4    2    1
Bit:       0    0    0    0    0    1    0    1  = 5
                                    โ†‘         โ†‘
                                    4    +    1  = 5

Signed Integers in JavaScript

JavaScript uses 32-bit signed integers for bitwise operations:

  • Positive numbers: 0 to 2,147,483,647 (2ยณยน - 1)
  • Negative numbers: -2,147,483,648 to -1 (-2ยณยน to -1)
  • Uses two's complement for negative numbers
// Positive number
5  โ†’ 0000 0000 0000 0000 0000 0000 0000 0101

// Negative number (two's complement)
-5 โ†’ 1111 1111 1111 1111 1111 1111 1111 1011

Bitwise AND (&)

The bitwise AND operator compares each bit of two numbers. The result bit is 1 only if both corresponding bits are 1.

Truth Table

Bit A  Bit B  A & B
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
  0      0      0
  0      1      0
  1      0      0
  1      1      1

Rule: Both bits must be 1 to get 1.

How It Works

  5 = 0000 0101
  3 = 0000 0011
      โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
5 & 3 = 0000 0001  = 1

// Step by step:
// Position 0: 1 & 1 = 1 โœ“
// Position 1: 0 & 1 = 0
// Position 2: 1 & 0 = 0
// Result: 1

Syntax and Examples

console.log(5 & 3);   // 1
console.log(12 & 10); // 8
console.log(7 & 4);   // 4
console.log(0 & 5);   // 0 (0 & anything = 0)
console.log(15 & 15); // 15 (same number)

// Binary visualization
console.log((0b1100 & 0b1010).toString(2)); // "1000" = 8

Practical Use Cases

1. Check if Number is Even or Odd:

function isEven(num) {
  return (num & 1) === 0;  // Last bit is 0 for even numbers
}

console.log(isEven(4));   // true
console.log(isEven(5));   // false
console.log(isEven(10));  // true

// Why it works:
// Even: 4 = 0100 โ†’ last bit is 0
// Odd:  5 = 0101 โ†’ last bit is 1

2. Check if Bit is Set:

function isBitSet(number, position) {
  return (number & (1 << position)) !== 0;
}

console.log(isBitSet(5, 0));  // true  (5 = 0101, bit 0 is 1)
console.log(isBitSet(5, 1));  // false (5 = 0101, bit 1 is 0)
console.log(isBitSet(5, 2));  // true  (5 = 0101, bit 2 is 1)

3. Extract Lower Bits:

// Get last 4 bits (mask with 0xF = 1111)
let value = 0b11010101;
let lowerBits = value & 0xF;  // 0b0101 = 5

console.log(lowerBits.toString(2).padStart(4, '0')); // "0101"

4. Color Masking (RGB):

// RGB color stored as single number: 0xRRGGBB
let color = 0xFF5733;  // Orange color

// Extract components
let red   = (color & 0xFF0000) >> 16;  // 0xFF = 255
let green = (color & 0x00FF00) >> 8;   // 0x57 = 87
let blue  = (color & 0x0000FF);        // 0x33 = 51

console.log(`RGB(${red}, ${green}, ${blue})`); // RGB(255, 87, 51)

Bitwise OR (|)

The bitwise OR operator compares each bit of two numbers. The result bit is 1 if either or both corresponding bits are 1.

Truth Table

Bit A  Bit B  A | B
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
  0      0      0
  0      1      1
  1      0      1
  1      1      1

Rule: At least one bit must be 1 to get 1.

How It Works

  5 = 0000 0101
  3 = 0000 0011
      โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
5 | 3 = 0000 0111  = 7

// Step by step:
// Position 0: 1 | 1 = 1
// Position 1: 0 | 1 = 1
// Position 2: 1 | 0 = 1
// Result: 7

Syntax and Examples

console.log(5 | 3);   // 7
console.log(12 | 10); // 14
console.log(4 | 2);   // 6
console.log(0 | 5);   // 5 (0 | anything = anything)
console.log(15 | 15); // 15 (same number)

// Binary visualization
console.log((0b1100 | 0b1010).toString(2)); // "1110" = 14

Practical Use Cases

1. Set a Specific Bit:

function setBit(number, position) {
  return number | (1 << position);
}

let flags = 0b0000;
flags = setBit(flags, 0);  // 0b0001 = 1
flags = setBit(flags, 2);  // 0b0101 = 5

console.log(flags.toString(2).padStart(4, '0')); // "0101"

2. Combine Flags/Permissions:

// Permission flags
const READ    = 0b001;  // 1
const WRITE   = 0b010;  // 2
const EXECUTE = 0b100;  // 4

// Grant multiple permissions
let permissions = READ | WRITE;  // 0b011 = 3
console.log(permissions); // 3

// Grant all permissions
let allPermissions = READ | WRITE | EXECUTE;  // 0b111 = 7
console.log(allPermissions); // 7

// Check permission
function hasPermission(userPerms, requiredPerm) {
  return (userPerms & requiredPerm) === requiredPerm;
}

console.log(hasPermission(permissions, READ));    // true
console.log(hasPermission(permissions, WRITE));   // true
console.log(hasPermission(permissions, EXECUTE)); // false

3. Merge RGB Components:

function createColor(red, green, blue) {
  return (red << 16) | (green << 8) | blue;
}

let color = createColor(255, 87, 51);  // 0xFF5733
console.log(color.toString(16).toUpperCase()); // "FF5733"

4. Round to Integer (Trick):

// Using bitwise OR with 0 to truncate decimals
console.log(3.7 | 0);    // 3
console.log(-3.7 | 0);   // -3
console.log(5.99 | 0);   // 5

// Faster than Math.floor for positive numbers
// But be careful with large numbers (32-bit limit)

Bitwise XOR (^)

The bitwise XOR (Exclusive OR) operator compares each bit of two numbers. The result bit is 1 if the bits are different.

Truth Table

Bit A  Bit B  A ^ B
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
  0      0      0
  0      1      1
  1      0      1
  1      1      0

Rule: Bits must be different to get 1.

How It Works

  5 = 0000 0101
  3 = 0000 0011
      โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
5 ^ 3 = 0000 0110  = 6

// Step by step:
// Position 0: 1 ^ 1 = 0 (same)
// Position 1: 0 ^ 1 = 1 (different)
// Position 2: 1 ^ 0 = 1 (different)
// Result: 6

Syntax and Examples

console.log(5 ^ 3);   // 6
console.log(12 ^ 10); // 6
console.log(7 ^ 4);   // 3
console.log(5 ^ 0);   // 5 (XOR with 0 = same number)
console.log(5 ^ 5);   // 0 (XOR with itself = 0)

// Binary visualization
console.log((0b1100 ^ 0b1010).toString(2)); // "110" = 6

Unique Properties of XOR

// Property 1: XOR with itself = 0
console.log(7 ^ 7);   // 0
console.log(99 ^ 99); // 0

// Property 2: XOR with 0 = same number
console.log(7 ^ 0);   // 7
console.log(99 ^ 0);  // 99

// Property 3: XOR is commutative and associative
console.log(5 ^ 3);       // 6
console.log(3 ^ 5);       // 6 (same)
console.log(5 ^ 3 ^ 2);   // 4
console.log((5 ^ 3) ^ 2); // 4
console.log(5 ^ (3 ^ 2)); // 4 (same)

// Property 4: XOR twice = original
let original = 42;
let key = 123;
let encrypted = original ^ key;
let decrypted = encrypted ^ key;
console.log(decrypted === original); // true

Practical Use Cases

1. Toggle a Bit:

function toggleBit(number, position) {
  return number ^ (1 << position);
}

let value = 0b0101;  // 5
value = toggleBit(value, 1);  // 0b0111 = 7
value = toggleBit(value, 1);  // 0b0101 = 5 (toggled back)

console.log(value); // 5

2. Simple Encryption (Very Basic):

function simpleEncrypt(text, key) {
  return text.split('').map(char => 
    String.fromCharCode(char.charCodeAt(0) ^ key)
  ).join('');
}

function simpleDecrypt(encrypted, key) {
  return simpleEncrypt(encrypted, key);  // XOR is symmetric!
}

let message = "Hello";
let key = 123;
let encrypted = simpleEncrypt(message, key);
let decrypted = simpleDecrypt(encrypted, key);

console.log("Original:", message);    // "Hello"
console.log("Encrypted:", encrypted); // Gibberish
console.log("Decrypted:", decrypted); // "Hello"

3. Swap Variables Without Temp:

let a = 5;
let b = 10;

console.log("Before:", a, b); // Before: 5 10

// Swap using XOR
a = a ^ b;  // a = 5 ^ 10
b = a ^ b;  // b = (5 ^ 10) ^ 10 = 5
a = a ^ b;  // a = (5 ^ 10) ^ 5 = 10

console.log("After:", a, b);  // After: 10 5

// Note: Modern JS has destructuring for this:
// [a, b] = [b, a];

4. Find the Unique Number:

// Find the number that appears only once (all others appear twice)
function findUnique(arr) {
  let result = 0;
  for (let num of arr) {
    result ^= num;  // XOR all numbers
  }
  return result;  // Duplicates cancel out, leaving unique
}

console.log(findUnique([2, 3, 5, 3, 2]));     // 5
console.log(findUnique([1, 2, 3, 4, 3, 2, 1])); // 4

// Why it works:
// 2 ^ 3 ^ 5 ^ 3 ^ 2
// = (2 ^ 2) ^ (3 ^ 3) ^ 5
// = 0 ^ 0 ^ 5
// = 5

5. Check if Two Numbers Have Opposite Signs:

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

console.log(haveOppositeSigns(5, -3));   // true
console.log(haveOppositeSigns(5, 3));    // false
console.log(haveOppositeSigns(-5, -3));  // false

Bitwise NOT (~)

The bitwise NOT operator inverts all bits (flips 1s to 0s and 0s to 1s). It's a unary operator.

How It Works

 5 = 0000 0000 0000 0000 0000 0000 0000 0101
~5 = 1111 1111 1111 1111 1111 1111 1111 1010 = -6

// In JavaScript: ~n = -(n + 1)

Syntax and Examples

console.log(~5);   // -6
console.log(~-3);  // 2
console.log(~0);   // -1
console.log(~-1);  // 0

// Formula: ~n = -(n + 1)
console.log(~10 === -11);  // true
console.log(~99 === -100); // true

Why ~n = -(n + 1)?

Due to two's complement representation of negative numbers:

// For number 5:
 5 = 0000 0101
~5 = 1111 1010  (invert all bits)

// 1111 1010 in two's complement:
// Sign bit is 1 (negative)
// To get value: invert bits and add 1
// ~(1111 1010) = 0000 0101 = 5
// Add 1: 5 + 1 = 6
// So 1111 1010 = -6

Practical Use Cases

1. Check if Index Exists (indexOf Trick):

let arr = ["apple", "banana", "orange"];

// Old way
if (arr.indexOf("banana") !== -1) {
  console.log("Found");
}

// Using bitwise NOT (less common, but works)
if (~arr.indexOf("banana")) {
  console.log("Found");  // ~(-1) = 0 (falsy), ~(index) is truthy
}

// Why it works:
// ~(-1) = 0 (falsy)
// ~0 = -1 (truthy)
// ~1 = -2 (truthy)
// etc.

// Note: Modern JS uses .includes() instead
if (arr.includes("banana")) {
  console.log("Found");  // โœ… Clearer and better
}

2. Fast Floor for Positive Numbers:

console.log(~~3.9);   // 3 (double NOT truncates decimals)
console.log(~~5.1);   // 5
console.log(~~-3.9);  // -3

// How it works:
// ~3.9 = ~3 = -4
// ~(-4) = 3

// Warning: Only works within 32-bit integer range
// Better to use Math.floor() for clarity

Left Shift (<<)

The left shift operator shifts bits to the left by specified positions. Empty positions are filled with 0.

How It Works

5 << 1:

Before: 0000 0101  (5)
After:  0000 1010  (10)
         โ†

Formula: n << x = n * 2^x

Syntax and Examples

console.log(5 << 1);   // 10 (5 * 2ยน = 10)
console.log(5 << 2);   // 20 (5 * 2ยฒ = 20)
console.log(5 << 3);   // 40 (5 * 2ยณ = 40)
console.log(1 << 4);   // 16 (1 * 2โด = 16)
console.log(3 << 5);   // 96 (3 * 2โต = 96)

// Binary visualization
console.log((0b101 << 2).toString(2));  // "10100" = 20

Practical Use Cases

1. Fast Multiplication by Power of 2:

// Instead of: num * 2
console.log(5 * 2);    // 10
console.log(5 << 1);   // 10 (faster)

// Instead of: num * 4
console.log(5 * 4);    // 20
console.log(5 << 2);   // 20 (faster)

// Instead of: num * 8
console.log(5 * 8);    // 40
console.log(5 << 3);   // 40 (faster)

2. Create Bit Masks:

// Create mask for bit position
function createMask(position) {
  return 1 << position;
}

console.log(createMask(0).toString(2)); // "1"       (0b0001)
console.log(createMask(1).toString(2)); // "10"      (0b0010)
console.log(createMask(2).toString(2)); // "100"     (0b0100)
console.log(createMask(3).toString(2)); // "1000"    (0b1000)

3. Power of 2 Calculator:

function powerOf2(exponent) {
  return 1 << exponent;
}

console.log(powerOf2(0));   // 1   (2โฐ)
console.log(powerOf2(1));   // 2   (2ยน)
console.log(powerOf2(2));   // 4   (2ยฒ)
console.log(powerOf2(10));  // 1024 (2ยนโฐ)

4. RGB to Hex Color:

function rgbToHex(r, g, b) {
  return '#' + ((r << 16) | (g << 8) | b).toString(16).padStart(6, '0');
}

console.log(rgbToHex(255, 87, 51));  // "#ff5733"
console.log(rgbToHex(0, 128, 255));  // "#0080ff"

Sign-Propagating Right Shift (>>)

The sign-propagating right shift shifts bits to the right. The leftmost bit (sign bit) is copied to fill empty positions.

How It Works

5 >> 1:

Before: 0000 0101  (5)
After:  0000 0010  (2)
             โ†’

-5 >> 1:

Before: 1111 1011  (-5)
After:  1111 1101  (-3)
        โ†“    โ†’
   (Sign bit preserved)

Formula: n >> x = Math.floor(n / 2^x)

Syntax and Examples

// Positive numbers
console.log(5 >> 1);   // 2 (5 / 2ยน = 2.5 โ†’ 2)
console.log(10 >> 1);  // 5 (10 / 2ยน = 5)
console.log(20 >> 2);  // 5 (20 / 2ยฒ = 5)
console.log(16 >> 3);  // 2 (16 / 2ยณ = 2)

// Negative numbers (sign preserved)
console.log(-5 >> 1);  // -3 (keeps negative)
console.log(-10 >> 1); // -5 (keeps negative)
console.log(-20 >> 2); // -5 (keeps negative)

// Binary visualization
console.log((0b1010 >> 1).toString(2));  // "101" = 5

Practical Use Cases

1. Fast Integer Division by Power of 2:

// Instead of: Math.floor(num / 2)
console.log(Math.floor(10 / 2));  // 5
console.log(10 >> 1);             // 5 (faster)

// Instead of: Math.floor(num / 4)
console.log(Math.floor(20 / 4));  // 5
console.log(20 >> 2);             // 5 (faster)

// Works with negative numbers too
console.log(Math.floor(-10 / 2)); // -5
console.log(-10 >> 1);            // -5

2. Extract High Bits:

// Extract upper byte from 16-bit number
let value = 0b1111000011110000;
let upperByte = value >> 8;  // 0b11110000 = 240

console.log(upperByte.toString(2).padStart(8, '0')); // "11110000"

3. RGB Color Extraction:

function extractRGB(color) {
  return {
    red:   (color >> 16) & 0xFF,
    green: (color >> 8) & 0xFF,
    blue:  color & 0xFF
  };
}

let color = 0xFF5733;
console.log(extractRGB(color)); // { red: 255, green: 87, blue: 51 }

Zero-Fill Right Shift (>>>)

The zero-fill right shift shifts bits to the right. Empty positions are always filled with 0, regardless of sign.

How It Works

5 >>> 1:

Before: 0000 0101  (5)
After:  0000 0010  (2)
        0    โ†’

-5 >>> 1:

Before: 1111 1011  (-5)
After:  0111 1101  (2,147,483,645)
        0    โ†’
   (0 filled, not sign bit!)

Syntax and Examples

// Positive numbers (same as >>)
console.log(5 >>> 1);   // 2
console.log(10 >>> 1);  // 5
console.log(20 >>> 2);  // 5

// Negative numbers (different from >>)
console.log(-5 >> 1);   // -3 (sign-propagating)
console.log(-5 >>> 1);  // 2147483645 (zero-fill, becomes huge positive!)

console.log(-1 >>> 0);  // 4294967295 (all bits set to 1)

Practical Use Cases

1. Convert to Unsigned 32-bit Integer:

function toUint32(num) {
  return num >>> 0;
}

console.log(toUint32(-1));    // 4294967295
console.log(toUint32(-5));    // 4294967291
console.log(toUint32(5));     // 5 (unchanged for positive)

// Useful for bit manipulations requiring unsigned values

2. Get Positive Hash Code:

function hashCode(str) {
  let hash = 0;
  for (let i = 0; i < str.length; i++) {
    hash = ((hash << 5) - hash) + str.charCodeAt(i);
    hash = hash >>> 0;  // Convert to unsigned 32-bit integer
  }
  return hash;
}

console.log(hashCode("hello"));     // 99162322
console.log(hashCode("world"));     // 113318802

Bitwise Assignment Operators

Like other operators, bitwise operators have assignment variants:

let x = 5;

// Bitwise AND assignment
x &= 3;   // x = x & 3
console.log(x); // 1

// Bitwise OR assignment
x = 5;
x |= 3;   // x = x | 3
console.log(x); // 7

// Bitwise XOR assignment
x = 5;
x ^= 3;   // x = x ^ 3
console.log(x); // 6

// Left shift assignment
x = 5;
x <<= 2;  // x = x << 2
console.log(x); // 20

// Right shift assignment
x = 20;
x >>= 2;  // x = x >> 2
console.log(x); // 5

// Zero-fill right shift assignment
x = -5;
x >>>= 1; // x = x >>> 1
console.log(x); // 2147483645

Real-World Applications

1. Permission System

// Define permissions as bit flags
const PERMISSIONS = {
  NONE:   0b0000,  // 0
  READ:   0b0001,  // 1
  WRITE:  0b0010,  // 2
  DELETE: 0b0100,  // 4
  ADMIN:  0b1000   // 8
};

class User {
  constructor(name) {
    this.name = name;
    this.permissions = PERMISSIONS.NONE;
  }
  
  // Grant permission
  grant(permission) {
    this.permissions |= permission;
  }
  
  // Revoke permission
  revoke(permission) {
    this.permissions &= ~permission;
  }
  
  // Check permission
  has(permission) {
    return (this.permissions & permission) === permission;
  }
  
  // Toggle permission
  toggle(permission) {
    this.permissions ^= permission;
  }
}

let user = new User("John");

// Grant permissions
user.grant(PERMISSIONS.READ);
user.grant(PERMISSIONS.WRITE);

console.log(user.has(PERMISSIONS.READ));   // true
console.log(user.has(PERMISSIONS.WRITE));  // true
console.log(user.has(PERMISSIONS.DELETE)); // false

// Revoke permission
user.revoke(PERMISSIONS.WRITE);
console.log(user.has(PERMISSIONS.WRITE));  // false

// Grant multiple at once
user.grant(PERMISSIONS.READ | PERMISSIONS.DELETE | PERMISSIONS.ADMIN);
console.log(user.has(PERMISSIONS.ADMIN));  // true

2. Feature Flags

const FEATURES = {
  DARK_MODE:        1 << 0,  // 1
  NOTIFICATIONS:    1 << 1,  // 2
  ANALYTICS:        1 << 2,  // 4
  EXPERIMENTAL:     1 << 3,  // 8
  BETA_FEATURES:    1 << 4,  // 16
  ADVANCED_SEARCH:  1 << 5   // 32
};

class FeatureManager {
  constructor() {
    this.flags = 0;
  }
  
  enable(feature) {
    this.flags |= feature;
  }
  
  disable(feature) {
    this.flags &= ~feature;
  }
  
  toggle(feature) {
    this.flags ^= feature;
  }
  
  isEnabled(feature) {
    return (this.flags & feature) !== 0;
  }
  
  enableMultiple(...features) {
    features.forEach(f => this.enable(f));
  }
  
  getEnabledFeatures() {
    let enabled = [];
    for (let [name, flag] of Object.entries(FEATURES)) {
      if (this.isEnabled(flag)) {
        enabled.push(name);
      }
    }
    return enabled;
  }
}

let features = new FeatureManager();

// Enable features
features.enable(FEATURES.DARK_MODE);
features.enable(FEATURES.NOTIFICATIONS);

console.log(features.isEnabled(FEATURES.DARK_MODE));      // true
console.log(features.isEnabled(FEATURES.ANALYTICS));      // false

// Toggle
features.toggle(FEATURES.DARK_MODE);
console.log(features.isEnabled(FEATURES.DARK_MODE));      // false

// Enable multiple
features.enableMultiple(
  FEATURES.DARK_MODE,
  FEATURES.ANALYTICS,
  FEATURES.BETA_FEATURES
);

console.log(features.getEnabledFeatures());
// ["DARK_MODE", "NOTIFICATIONS", "ANALYTICS", "BETA_FEATURES"]

3. IP Address Manipulation

class IPAddress {
  constructor(ip) {
    if (typeof ip === 'string') {
      // Convert string to number
      let parts = ip.split('.').map(Number);
      this.value = (parts[0] << 24) | (parts[1] << 16) | 
                   (parts[2] << 8) | parts[3];
    } else {
      this.value = ip >>> 0;  // Ensure unsigned 32-bit
    }
  }
  
  toString() {
    return [
      (this.value >>> 24) & 0xFF,
      (this.value >>> 16) & 0xFF,
      (this.value >>> 8) & 0xFF,
      this.value & 0xFF
    ].join('.');
  }
  
  isInSubnet(subnet, mask) {
    let subnetIP = new IPAddress(subnet);
    let maskBits = (0xFFFFFFFF << (32 - mask)) >>> 0;
    
    return (this.value & maskBits) === (subnetIP.value & maskBits);
  }
}

let ip1 = new IPAddress("192.168.1.100");
let ip2 = new IPAddress("192.168.1.200");
let ip3 = new IPAddress("192.168.2.100");

console.log(ip1.toString());  // "192.168.1.100"

// Check if in same /24 subnet (255.255.255.0)
console.log(ip1.isInSubnet("192.168.1.0", 24));  // true
console.log(ip3.isInSubnet("192.168.1.0", 24));  // false

4. Color Manipulation

class Color {
  constructor(r, g, b) {
    this.value = ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF);
  }
  
  get red() {
    return (this.value >> 16) & 0xFF;
  }
  
  get green() {
    return (this.value >> 8) & 0xFF;
  }
  
  get blue() {
    return this.value & 0xFF;
  }
  
  toHex() {
    return '#' + this.value.toString(16).padStart(6, '0');
  }
  
  lighten(amount) {
    let r = Math.min(255, this.red + amount);
    let g = Math.min(255, this.green + amount);
    let b = Math.min(255, this.blue + amount);
    return new Color(r, g, b);
  }
  
  darken(amount) {
    let r = Math.max(0, this.red - amount);
    let g = Math.max(0, this.green - amount);
    let b = Math.max(0, this.blue - amount);
    return new Color(r, g, b);
  }
  
  invert() {
    return new Color(255 - this.red, 255 - this.green, 255 - this.blue);
  }
  
  static fromHex(hex) {
    let value = parseInt(hex.replace('#', ''), 16);
    return new Color(
      (value >> 16) & 0xFF,
      (value >> 8) & 0xFF,
      value & 0xFF
    );
  }
}

let color = new Color(255, 87, 51);  // Orange
console.log(color.toHex());          // "#ff5733"

let lighter = color.lighten(50);
console.log(lighter.toHex());        // "#ff8959"

let darker = color.darken(50);
console.log(darker.toHex());         // "#cb2301"

let inverted = color.invert();
console.log(inverted.toHex());       // "#00a8cc"

let fromHex = Color.fromHex("#ff5733");
console.log(fromHex.red, fromHex.green, fromHex.blue); // 255 87 51

Performance Considerations

When Bitwise Operations are Faster

// Bitwise operations are faster for:

// 1. Integer division/multiplication by powers of 2
console.time('Division');
for (let i = 0; i < 1000000; i++) {
  let result = Math.floor(i / 2);
}
console.timeEnd('Division');

console.time('Right Shift');
for (let i = 0; i < 1000000; i++) {
  let result = i >> 1;
}
console.timeEnd('Right Shift');  // Typically faster

// 2. Checking even/odd
console.time('Modulo');
for (let i = 0; i < 1000000; i++) {
  let isEven = i % 2 === 0;
}
console.timeEnd('Modulo');

console.time('Bitwise AND');
for (let i = 0; i < 1000000; i++) {
  let isEven = (i & 1) === 0;
}
console.timeEnd('Bitwise AND');  // Typically faster

// 3. Rounding to integer
console.time('Math.floor');
for (let i = 0; i < 1000000; i++) {
  let result = Math.floor(i * 0.5);
}
console.timeEnd('Math.floor');

console.time('Bitwise OR');
for (let i = 0; i < 1000000; i++) {
  let result = (i * 0.5) | 0;
}
console.timeEnd('Bitwise OR');  // Typically faster

When to Use Bitwise Operations

โœ… Use when:

  • Working with flags/permissions
  • Manipulating colors (RGB)
  • Network programming (IP addresses, ports)
  • Performance-critical loops with simple integer math
  • Low-level data manipulation

โŒ Avoid when:

  • Code clarity is more important than micro-optimizations
  • Working with large numbers (>32-bit integers)
  • Team members unfamiliar with bitwise operations
  • Premature optimization (profile first!)

Common Mistakes and Pitfalls

Mistake 1: 32-bit Integer Overflow

// โŒ Problem - limited to 32-bit
let large = 5000000000;  // 5 billion
console.log(large << 1); // -1589934592 (overflow!)

// โœ… Use regular multiplication for large numbers
console.log(large * 2);  // 10000000000 (correct)

Mistake 2: Using Bitwise with Decimals

// โŒ Decimals are truncated
console.log(5.7 & 3.2);  // 1 (5 & 3)
console.log(10.9 | 4.1); // 14 (10 | 4)

// Be aware of truncation
console.log(3.9 | 0);    // 3 (truncates, doesn't round)

Mistake 3: Sign Bit Confusion

// โŒ Forgetting about sign bit
let value = 0x80000000;  // Largest positive in 31 bits
console.log(value);      // -2147483648 (negative due to sign bit!)

// โœ… Use >>> for unsigned
console.log(value >>> 0); // 2147483648 (unsigned)

Mistake 4: Incorrect Operator Precedence

// โŒ Wrong precedence
console.log(5 & 3 === 1);  // false (5 & (3 === 1))

// โœ… Use parentheses
console.log((5 & 3) === 1); // true

Mistake 5: Using Bitwise for Boolean Logic

// โŒ Confusing bitwise with logical
let a = true;
let b = false;
console.log(a & b);  // 0 (not false!)
console.log(a | b);  // 1 (not true!)

// โœ… Use logical operators for booleans
console.log(a && b); // false
console.log(a || b); // true

Best Practices

1. Use Named Constants for Flags

// โŒ Hard to understand
let user = 7;  // What does 7 mean?

// โœ… Clear and self-documenting
const READ = 1;
const WRITE = 2;
const DELETE = 4;
let user = READ | WRITE | DELETE;  // 7, but clear intent

2. Comment Bitwise Code

// โŒ No explanation
let result = (value & 0xFF00) >> 8;

// โœ… Explain what's happening
// Extract upper byte (bits 8-15) from 16-bit value
let upperByte = (value & 0xFF00) >> 8;

3. Prefer Clarity Over Micro-Optimization

// โŒ Clever but unclear
let isEven = !(n & 1);

// โœ… Clear and readable (compiler optimizes anyway)
let isEven = n % 2 === 0;

// Only use bitwise if:
// 1. Profiling shows it's a bottleneck
// 2. Team understands bitwise operations
// 3. You're working with actual bits (flags, colors, etc.)

4. Use Unsigned Right Shift for Unsigned Values

// โŒ Can produce negative results
let hash = (hash << 5) - hash;

// โœ… Ensure unsigned with >>>
let hash = ((hash << 5) - hash) >>> 0;

5. Create Helper Functions

// โŒ Repeating bitwise logic
let r = (color >> 16) & 0xFF;
let g = (color >> 8) & 0xFF;
let b = color & 0xFF;

// โœ… Encapsulate in functions
function getRed(color) { return (color >> 16) & 0xFF; }
function getGreen(color) { return (color >> 8) & 0xFF; }
function getBlue(color) { return color & 0xFF; }

let r = getRed(color);
let g = getGreen(color);
let b = getBlue(color);

Summary

Congratulations! ๐ŸŽ‰ You now have a comprehensive understanding of bitwise operators in JavaScript.

Key Takeaways

โœ… Bitwise AND (&)

  • Both bits must be 1
  • Check bits, masks, permissions

โœ… Bitwise OR (|)

  • At least one bit must be 1
  • Set bits, combine flags

โœ… Bitwise XOR (^)

  • Bits must be different
  • Toggle bits, encryption, swapping

โœ… Bitwise NOT (~)

  • Inverts all bits
  • Formula: ~n = -(n + 1)

โœ… Left Shift (<<)

  • Shifts left, fills with 0
  • Fast multiply by power of 2

โœ… Right Shift (>>)

  • Shifts right, preserves sign
  • Fast divide by power of 2

โœ… Zero-fill Right Shift (>>>)

  • Shifts right, fills with 0
  • Convert to unsigned

โœ… Common Uses:

  • Permission/flag systems
  • Color manipulation
  • Performance optimization
  • Low-level data handling

โœ… Best Practices:

  • Use named constants
  • Comment bitwise code
  • Prefer clarity over micro-optimization
  • Be aware of 32-bit limits

What's Next?

Excellent work! You now understand bitwise operations at a deep level.

In the next lesson, we'll explore Special Operators including:

  • Ternary operator (?:)
  • typeof operator
  • instanceof operator
  • void operator
  • delete operator
  • in operator
  • comma operator (,)
  • Optional chaining (?.)
  • Spread operator (...)