Bitwise Operators

Vaibhav • September 13, 2025

While most programming operations work with whole numbers, strings, and other high-level data types, computers fundamentally operate on binary data – sequences of 1s and 0s called bits. Bitwise operators allow you to manipulate individual bits within integer values, providing a way to work directly with the binary representation of numbers.

Though bitwise operations might seem advanced or esoteric, they're actually fundamental to how computers work and have practical applications in many areas of programming, including graphics, cryptography, optimization, and system-level programming. Understanding bitwise operators gives you insight into how computers actually process data at the lowest level.

Understanding binary representation

Before exploring bitwise operators, it's essential to understand how computers represent numbers in binary. Every integer in a computer is stored as a sequence of bits, where each bit can be either 0 or 1. This binary representation is the foundation for all bitwise operations.

For example, the decimal number 5 is represented in binary as 101, which means (1×4) + (0×2) + (1×1) = 5. The decimal number 12 is represented as 1100, which means (1×8) + (1×4) + (0×2) + (0×1) = 12.

// Let's work with some integers and understand their binary representation
int number1 = 5;    // Binary: 101
int number2 = 12;   // Binary: 1100
int number3 = 7;    // Binary: 111
int number4 = 15;   // Binary: 1111

Console.WriteLine("Decimal 5 in binary is conceptually: 101");
Console.WriteLine("Decimal 12 in binary is conceptually: 1100");
Console.WriteLine("Decimal 7 in binary is conceptually: 111");
Console.WriteLine("Decimal 15 in binary is conceptually: 1111");

// We'll use these numbers to demonstrate bitwise operations
Console.WriteLine("Working with: " + number1 + ", " + number2 + ", " + number3 + ", " + number4);

In C#, integers are typically stored as 32-bit values, meaning each integer uses 32 bits (binary digits) to represent its value. However, for simplicity in our examples, we'll focus on the relevant bits and understand that leading zeros are implied.

The six bitwise operators

C# provides six bitwise operators for manipulating individual bits within integer values:

  • Bitwise AND (&) - sets each bit to 1 if both bits are 1
  • Bitwise OR (|) - sets each bit to 1 if at least one bit is 1
  • Bitwise XOR (^) - sets each bit to 1 if the bits are different
  • Bitwise NOT (~) - inverts all bits (0 becomes 1, 1 becomes 0)
  • Left shift (<<) - shifts bits to the left
  • Right shift (>>) - shifts bits to the right

These operators work on the binary representation of numbers, performing operations on corresponding bit positions to produce a new binary result.

Bitwise AND operator (&)

The bitwise AND operator performs a logical AND operation on each pair of corresponding bits. The result bit is 1 only if both input bits are 1. If either bit is 0, the result bit is 0.

Think of bitwise AND like an intersection – only the positions where both numbers have a 1 bit will have a 1 in the result. This operation is commonly used for masking, where you want to isolate specific bits while clearing others.

int a = 12;  // Binary: 1100
int b = 7;   // Binary: 0111

int result = a & b;  // Bitwise AND
Console.WriteLine("Bitwise AND:");
Console.WriteLine(a + " & " + b + " = " + result);  // 12 & 7 = 4

// Let's trace through this bit by bit:
// 12 in binary: 1100
//  7 in binary: 0111
// AND result:   0100  (which is 4 in decimal)

// More examples
int mask = 15;  // Binary: 1111 (often used as a mask)
int value1 = 23; // Binary: 10111
int masked1 = value1 & mask;  // Keep only the last 4 bits
Console.WriteLine(value1 + " & " + mask + " = " + masked1);  // 23 & 15 = 7

// Checking if a specific bit is set
int number = 5;  // Binary: 101
int bitPosition2 = 4;  // Binary: 100 (checks bit position 2)
int bitCheck = number & bitPosition2;
bool isBitSet = bitCheck != 0;
Console.WriteLine("Is bit 2 set in " + number + "? " + isBitSet);  // Is bit 2 set in 5? True

Bitwise OR operator (|)

The bitwise OR operator performs a logical OR operation on each pair of corresponding bits. The result bit is 1 if at least one of the input bits is 1. The result bit is 0 only when both input bits are 0.

Bitwise OR is like a union operation – any position where either number has a 1 bit will have a 1 in the result. This operation is commonly used to set specific bits or combine bit patterns.

int a = 12;  // Binary: 1100
int b = 7;   // Binary: 0111

int result = a | b;  // Bitwise OR
Console.WriteLine("Bitwise OR:");
Console.WriteLine(a + " | " + b + " = " + result);  // 12 | 7 = 15

// Let's trace through this bit by bit:
// 12 in binary: 1100
//  7 in binary: 0111
//  OR result:   1111  (which is 15 in decimal)

// Setting specific bits
int baseValue = 8;   // Binary: 1000
int setBit = 2;      // Binary: 0010 (set bit position 1)
int withBitSet = baseValue | setBit;
Console.WriteLine("Setting bit: " + baseValue + " | " + setBit + " = " + withBitSet);  // Setting bit: 8 | 2 = 10

// Combining multiple bit flags
int flag1 = 1;   // Binary: 0001
int flag2 = 4;   // Binary: 0100
int flag3 = 8;   // Binary: 1000
int allFlags = flag1 | flag2 | flag3;
Console.WriteLine("Combined flags: " + flag1 + " | " + flag2 + " | " + flag3 + " = " + allFlags);  // Combined flags: 1 | 4 | 8 = 13

Bitwise XOR operator (^)

The bitwise XOR (exclusive OR) operator performs a logical XOR operation on each pair of corresponding bits. The result bit is 1 if the bits are different (one is 0 and the other is 1). The result bit is 0 if both bits are the same (both 0 or both 1).

XOR is useful for toggling bits, encryption operations, and detecting differences between bit patterns. It has the special property that XORing a value with itself always results in 0, and XORing a value with 0 leaves the value unchanged.

int a = 12;  // Binary: 1100
int b = 7;   // Binary: 0111

int result = a ^ b;  // Bitwise XOR
Console.WriteLine("Bitwise XOR:");
Console.WriteLine(a + " ^ " + b + " = " + result);  // 12 ^ 7 = 11

// Let's trace through this bit by bit:
// 12 in binary: 1100
//  7 in binary: 0111
// XOR result:   1011  (which is 11 in decimal)

// XOR special properties
int value = 42;
int xorWithSelf = value ^ value;  // Always 0
int xorWithZero = value ^ 0;      // Always the original value
Console.WriteLine(value + " ^ " + value + " = " + xorWithSelf);  // 42 ^ 42 = 0
Console.WriteLine(value + " ^ 0 = " + xorWithZero);              // 42 ^ 0 = 42

// Toggling specific bits
int originalValue = 5;  // Binary: 101
int toggleMask = 3;     // Binary: 011 (toggle first two bits)
int toggled = originalValue ^ toggleMask;
Console.WriteLine("Toggling bits: " + originalValue + " ^ " + toggleMask + " = " + toggled);  // Toggling bits: 5 ^ 3 = 6

// XOR twice returns to original (useful for simple encryption)
int secret = 123;
int key = 99;
int encrypted = secret ^ key;
int decrypted = encrypted ^ key;  // Same key to decrypt
Console.WriteLine("Original: " + secret);
Console.WriteLine("Encrypted: " + encrypted);
Console.WriteLine("Decrypted: " + decrypted);  // Should match original

Bitwise NOT operator (~)

The bitwise NOT operator is a unary operator that inverts all bits in its operand. Every 0 bit becomes 1, and every 1 bit becomes 0. This operation is also called the bitwise complement.

Because integers in C# are signed and use two's complement representation, the NOT operator can produce surprising results for beginners. The NOT of a positive number is always a negative number.

int value = 5;  // Binary: ...00000101 (simplified representation)
int notValue = ~value;  // Bitwise NOT
Console.WriteLine("Bitwise NOT:");
Console.WriteLine("~" + value + " = " + notValue);  // ~5 = -6

// The result is negative because of two's complement representation
// For positive integers, ~n = -(n+1)

int positiveNum = 10;
int notPositive = ~positiveNum;
Console.WriteLine("~" + positiveNum + " = " + notPositive);  // ~10 = -11

int anotherNum = 0;
int notZero = ~anotherNum;
Console.WriteLine("~" + anotherNum + " = " + notZero);  // ~0 = -1

// NOT is useful in combination with other operations
// For example, clearing specific bits using AND with NOT
int original = 15;    // Binary: 1111
int clearMask = 4;    // Binary: 0100 (bit to clear)
int cleared = original & ~clearMask;  // Clear bit 2
Console.WriteLine("Clearing bit: " + original + " & ~" + clearMask + " = " + cleared);  // Clearing bit: 15 & ~4 = 11

Left shift operator (<<)

The left shift operator moves all bits in a number to the left by a specified number of positions. Zeros are shifted in from the right, and bits that are shifted beyond the leftmost position are discarded. Each left shift by one position effectively multiplies the number by 2.

int value = 5;  // Binary: 101

int shifted1 = value << 1;  // Shift left by 1 position
Console.WriteLine("Left shift by 1:");
Console.WriteLine(value + " << 1 = " + shifted1);  // 5 << 1 = 10

// 5 in binary:      101
// After << 1:      1010  (which is 10 in decimal)

int shifted2 = value << 2;  // Shift left by 2 positions  
Console.WriteLine(value + " << 2 = " + shifted2);  // 5 << 2 = 20

// 5 in binary:      101
// After << 2:     10100  (which is 20 in decimal)

int shifted3 = value << 3;  // Shift left by 3 positions
Console.WriteLine(value + " << 3 = " + shifted3);  // 5 << 3 = 40

// Left shift as multiplication by powers of 2
int number = 7;
Console.WriteLine("Using left shift for multiplication:");
Console.WriteLine(number + " * 2 = " + (number << 1));   // 7 * 2 = 14
Console.WriteLine(number + " * 4 = " + (number << 2));   // 7 * 4 = 28  
Console.WriteLine(number + " * 8 = " + (number << 3));   // 7 * 8 = 56

// Practical example: calculating powers of 2
int powerOf2_3 = 1 << 3;  // 2^3 = 8
int powerOf2_5 = 1 << 5;  // 2^5 = 32
Console.WriteLine("2^3 = " + powerOf2_3);
Console.WriteLine("2^5 = " + powerOf2_5);

Right shift operator (>>)

The right shift operator moves all bits in a number to the right by a specified number of positions. For positive numbers, zeros are shifted in from the left. Each right shift by one position effectively divides the number by 2 (with truncation toward zero).

int value = 20;  // Binary: 10100

int shifted1 = value >> 1;  // Shift right by 1 position
Console.WriteLine("Right shift by 1:");
Console.WriteLine(value + " >> 1 = " + shifted1);  // 20 >> 1 = 10

// 20 in binary:     10100
// After >> 1:        1010  (which is 10 in decimal)

int shifted2 = value >> 2;  // Shift right by 2 positions
Console.WriteLine(value + " >> 2 = " + shifted2);  // 20 >> 2 = 5

// 20 in binary:     10100
// After >> 2:         101  (which is 5 in decimal)

int shifted3 = value >> 3;  // Shift right by 3 positions
Console.WriteLine(value + " >> 3 = " + shifted3);  // 20 >> 3 = 2

// Right shift as division by powers of 2
int number = 32;
Console.WriteLine("Using right shift for division:");
Console.WriteLine(number + " / 2 = " + (number >> 1));   // 32 / 2 = 16
Console.WriteLine(number + " / 4 = " + (number >> 2));   // 32 / 4 = 8
Console.WriteLine(number + " / 8 = " + (number >> 3));   // 32 / 8 = 4

// Right shift with truncation
int oddNumber = 15;  // Binary: 1111
int halfOdd = oddNumber >> 1;
Console.WriteLine("Odd number division: " + oddNumber + " >> 1 = " + halfOdd);  // 15 >> 1 = 7 (truncated)

Practical applications of bitwise operators

While bitwise operators might seem abstract, they have many practical applications in programming. Here are some common use cases that demonstrate their utility:

// Example 1: Using bits as flags (permissions system)
int readPermission = 1;   // Binary: 001
int writePermission = 2;  // Binary: 010  
int executePermission = 4; // Binary: 100

// Combine permissions using OR
int fullPermissions = readPermission | writePermission | executePermission;
Console.WriteLine("Full permissions value: " + fullPermissions);  // 7

// Check if a specific permission is set using AND
bool canRead = (fullPermissions & readPermission) != 0;
bool canWrite = (fullPermissions & writePermission) != 0;
bool canExecute = (fullPermissions & executePermission) != 0;

Console.WriteLine("Can read: " + canRead);       // Can read: True
Console.WriteLine("Can write: " + canWrite);     // Can write: True  
Console.WriteLine("Can execute: " + canExecute); // Can execute: True

// Remove a permission using AND with NOT
int withoutWritePermission = fullPermissions & ~writePermission;
bool stillCanWrite = (withoutWritePermission & writePermission) != 0;
Console.WriteLine("After removing write permission, can write: " + stillCanWrite);  // False

// Example 2: Efficient multiplication and division by powers of 2
int originalValue = 17;
int doubled = originalValue << 1;      // Multiply by 2
int quadrupled = originalValue << 2;   // Multiply by 4
int halved = originalValue >> 1;       // Divide by 2 (truncated)

Console.WriteLine("Original: " + originalValue);   // 17
Console.WriteLine("Doubled: " + doubled);          // 34
Console.WriteLine("Quadrupled: " + quadrupled);    // 68
Console.WriteLine("Halved: " + halved);            // 8 (truncated from 8.5)

// Example 3: Checking if a number is even or odd using bitwise AND
int testNumber1 = 42;
int testNumber2 = 43;

bool isEven1 = (testNumber1 & 1) == 0;  // Check if last bit is 0
bool isEven2 = (testNumber2 & 1) == 0;  // Check if last bit is 0

Console.WriteLine(testNumber1 + " is even: " + isEven1);  // 42 is even: True
Console.WriteLine(testNumber2 + " is even: " + isEven2);  // 43 is even: False

Bitwise vs logical operators

It's important not to confuse bitwise operators with logical operators. While they may look similar, they serve very different purposes:

  • Logical operators (&&, ||, !) work with boolean values and are used for conditional logic
  • Bitwise operators (&, |, ^, ~) work with individual bits in integer values
// Logical operators work with boolean values
bool condition1 = true;
bool condition2 = false;
bool logicalAnd = condition1 && condition2;  // false
bool logicalOr = condition1 || condition2;   // true

Console.WriteLine("Logical AND: " + logicalAnd);  // Logical AND: False
Console.WriteLine("Logical OR: " + logicalOr);    // Logical OR: True

// Bitwise operators work with integer bits
int num1 = 5;   // Binary: 101
int num2 = 3;   // Binary: 011
int bitwiseAnd = num1 & num2;  // 1 (binary: 001)
int bitwiseOr = num1 | num2;   // 7 (binary: 111)

Console.WriteLine("Bitwise AND: " + bitwiseAnd);  // Bitwise AND: 1
Console.WriteLine("Bitwise OR: " + bitwiseOr);    // Bitwise OR: 7

// Don't mix them up!
// This would be incorrect usage:
// bool wrongResult = num1 && num2;  // This won't work as expected

Don't confuse bitwise operators (&, |) with logical operators (&&, ||). Bitwise operators work on individual bits of integers, while logical operators work on boolean values for conditional logic.

Performance considerations

Bitwise operations are among the fastest operations a computer can perform because they directly correspond to fundamental processor instructions. This makes them valuable for performance-critical code, though readability should also be considered.

// Fast operations using bitwise operators
int number = 100;

// These bitwise operations are very fast:
int multiplyBy8 = number << 3;    // Much faster than number * 8
int divideBy4 = number >> 2;      // Much faster than number / 4
bool isEven = (number & 1) == 0;  // Faster than number % 2 == 0

Console.WriteLine("Original: " + number);
Console.WriteLine("Multiply by 8: " + multiplyBy8);   // 800
Console.WriteLine("Divide by 4: " + divideBy4);       // 25
Console.WriteLine("Is even: " + isEven);               // True

// However, prioritize readability unless performance is critical
// These are more readable for most programmers:
int regularMultiply = number * 8;
int regularDivide = number / 4;
bool regularEvenCheck = number % 2 == 0;

Console.WriteLine("Regular operations give same results:");
Console.WriteLine("Regular multiply: " + regularMultiply);
Console.WriteLine("Regular divide: " + regularDivide);
Console.WriteLine("Regular even check: " + regularEvenCheck);

Common mistakes and best practices

When working with bitwise operators, there are several important considerations:

Understand signed integer representation: Negative numbers use two's complement representation, which can make bitwise operations on negative numbers produce unexpected results for beginners.

Be careful with shift amounts: Shifting by more positions than the integer has bits can produce undefined behavior or unexpected results.

Consider readability: While bitwise operations can be more efficient, they can also make code harder to understand. Use them when appropriate, but don't sacrifice clarity unnecessarily.

Bitwise operations are excellent for working with flags, permissions, and other scenarios where you need to pack multiple boolean values into a single integer efficiently. They're also useful for performance-critical mathematical operations involving powers of 2.

Summary

Bitwise operators provide powerful tools for manipulating individual bits within integer values. While they might seem complex at first, understanding them gives you insight into how computers work at a fundamental level and provides efficient solutions for specific programming problems.

Key points to remember:

  • Bitwise operators work on the binary representation of integers
  • AND (&) is used for masking and checking specific bits
  • OR (|) is used for setting bits or combining bit patterns
  • XOR (^) is used for toggling bits and simple encryption
  • NOT (~) inverts all bits in a value
  • Left shift (<<) multiplies by powers of 2
  • Right shift (>>) divides by powers of 2 with truncation
  • Don't confuse bitwise operators with logical operators
  • Bitwise operations are very fast but may sacrifice readability

While bitwise operators might not be used in every program you write, understanding them is valuable for system programming, optimization, and gaining a deeper understanding of how computers manipulate data. In our next lesson, we'll explore operator precedence in detail, ensuring you understand how different operators interact when used together in complex expressions.