Python Bitwise Operators

If you have ever wondered how computers work at the hardware level, Python bitwise operators are your first real look into that world. These operators let you manipulate the individual bits of an integer directly, which is something that comes in handy more often than you might expect — from working with hardware registers to implementing compact data structures.

Python bitwise operators work on integers and treat them as sequences of binary digits. Each bit is either 0 or 1, and the bitwise operators apply logical rules across those bits position by position. Once you understand how binary numbers work, these operators become intuitive and surprisingly fun to use.

Binary Representation in Python

Before jumping into the operators themselves, it helps to understand how Python stores integers in binary. Every integer you write — like 5 or 12 — is stored in memory as a series of 0s and 1s. For example, the number 5 in binary is 101, and 12 in binary is 1100.

Python gives you built-in functions to inspect this. The bin() function converts an integer to its binary string representation, and int() can convert a binary string back to an integer.

a = 5
b = 12

print(bin(a))   # Binary of 5
print(bin(b))   # Binary of 12
print(int('1010', 2))  # Convert binary string '1010' back to int
0b101
0b1100
10

The "0b" prefix just means the value is in base 2 (binary). When you use Python bitwise operators, all the operations happen on these binary representations behind the scenes.

Python Bitwise AND Operator

The bitwise AND operator uses the symbol &. It compares each bit of two numbers and returns a 1 only when both corresponding bits are 1. If either bit is 0, the result is 0 for that position.

Think of it like a strict filter — both conditions must be true for the result to be true. This is extremely useful for bit masking, where you want to check whether specific bits in a number are turned on.

a = 60   # Binary: 0011 1100
b = 13   # Binary: 0000 1101

result = a & b
print(f"{a} & {b} = {result}")
print(f"Binary: {bin(a)} & {bin(b)} = {bin(result)}")
60 & 13 = 12
Binary: 0b111100 & 0b1101 = 0b1100

Let's walk through what happened. The bits of 60 are 111100 and the bits of 13 are 001101. When you align them and apply AND, only positions where both are 1 stay as 1. The rest become 0. That gives you 001100, which is 12 in decimal.

A real-world use case for the AND operator is checking whether a number is even or odd. An odd number always has its least significant bit (the rightmost bit) set to 1. So doing number & 1 tells you that fact immediately.

def check_even_odd(n):
    if n & 1:
        print(f"{n} is odd")
    else:
        print(f"{n} is even")

check_even_odd(7)
check_even_odd(14)
check_even_odd(25)
7 is odd
14 is even
25 is odd

Python Bitwise OR Operator

The bitwise OR operator uses the symbol |. It returns 1 if at least one of the two corresponding bits is 1. It only returns 0 when both bits are 0.

This operator is often used to combine flags or set specific bits in a value. Imagine you have a set of permission flags stored as individual bits — you can use OR to grant multiple permissions at once.

read_permission    = 0b0001   # 1
write_permission   = 0b0010   # 2
execute_permission = 0b0100   # 4

# Grant read and write
user_permissions = read_permission | write_permission
print(f"Permissions value: {user_permissions}")
print(f"Binary: {bin(user_permissions)}")

# Grant all three
full_permissions = read_permission | write_permission | execute_permission
print(f"Full permissions: {full_permissions}")
print(f"Binary: {bin(full_permissions)}")
Permissions value: 3
Binary: 0b11
Full permissions: 7
Binary: 0b111

Here you can see how OR combines the individual permission bits into a single integer. The value 7 (binary 111) means all three permissions are active. This pattern of storing multiple boolean flags in one integer is called a bitmask, and it is a technique used heavily in operating systems and networking code.

Python Bitwise XOR Operator

The bitwise XOR (exclusive OR) operator uses the symbol ^. It returns 1 only when the two corresponding bits are different. If both bits are the same (both 0 or both 1), the result is 0.

XOR has some elegant mathematical properties. One of the most useful is that XOR-ing a number with itself always gives 0, and XOR-ing a number with 0 leaves it unchanged. This makes it handy for toggling bits on and off.

a = 42   # Binary: 101010
b = 27   # Binary: 011011

result = a ^ b
print(f"{a} ^ {b} = {result}")
print(f"Binary: {bin(a)} ^ {bin(b)} = {bin(result)}")

# Toggle specific bits using XOR
mask = 0b1111   # Toggle the last 4 bits
value = 0b10110101
toggled = value ^ mask
print(f"\nOriginal: {bin(value)}")
print(f"After toggle: {bin(toggled)}")
42 ^ 27 = 49
Binary: 0b101010 ^ 0b11011 = 0b110001

Original: 0b10110101
After toggle: 0b10111010

XOR is also the foundation of a classic algorithm to find the one unique number in a list where every other number appears exactly twice. Since duplicates cancel each other out via XOR, only the unique number survives.

def find_unique(nums):
    result = 0
    for n in nums:
        result ^= n
    return result

numbers = [3, 7, 3, 9, 7, 5, 9]
print(f"Unique number: {find_unique(numbers)}")
Unique number: 5

Python Bitwise NOT Operator

The bitwise NOT operator uses the symbol ~. It flips every single bit in the number — every 0 becomes 1, and every 1 becomes 0. This is also called the bitwise complement.

In Python, the result of ~n is always -(n + 1). This happens because of how Python represents negative integers using two's complement notation. For example, ~5 gives -6, and ~0 gives -1.

a = 5

result = ~a
print(f"~{a} = {result}")
print(f"Explanation: ~5 = -(5 + 1) = -6")

# More examples
for val in [0, 1, 10, 255]:
    print(f"~{val} = {~val}")
~5 = -6
Explanation: ~5 = -(5 + 1) = -6
~0 = -1
~1 = -2
~10 = -11
~255 = -256

This behavior comes directly from the two's complement representation used by most modern computers and programming languages including Python. When you flip all the bits of a positive number, you get the negative version of that number minus one.

Left Shift and Right Shift Operators

Python has two shift operators that move bits to the left or right within a number. These are among the most practically useful Python bitwise operators for fast arithmetic operations.

Left Shift in Python

The left shift operator is written as <<. It shifts all bits to the left by a specified number of positions, filling vacated positions on the right with zeros. Each left shift by one position is equivalent to multiplying the number by 2.

a = 3        # Binary: 011
result = a << 1   # Shift left by 1: 110

print(f"{a} << 1 = {result}")
print(f"Binary: {bin(a)} << 1 = {bin(result)}")

# Shifting by multiple positions
print(f"\n1 << 0 = {1 << 0}")
print(f"1 << 1 = {1 << 1}")
print(f"1 << 2 = {1 << 2}")
print(f"1 << 3 = {1 << 3}")
print(f"1 << 4 = {1 << 4}")
3 << 1 = 6
Binary: 0b11 << 1 = 0b110

1 << 0 = 1
1 << 1 = 2
1 << 2 = 4
1 << 3 = 8
1 << 4 = 16

You can see that shifting 1 left repeatedly gives you powers of 2. This is a fast way to compute powers of 2 without calling any math function. It is also how you create single-bit masks positioned at a specific bit index.

Right Shift in Python

The right shift operator is written as >>. It shifts all bits to the right by a specified number of positions. Bits that fall off the right end are discarded. Each right shift by one position is equivalent to integer division by 2 (floor division).

a = 64   # Binary: 1000000

print(f"{a} >> 1 = {a >> 1}")
print(f"{a} >> 2 = {a >> 2}")
print(f"{a} >> 3 = {a >> 3}")
print(f"{a} >> 6 = {a >> 6}")

# Right shift on an odd number drops the remainder
b = 17
print(f"\n17 >> 1 = {b >> 1}  (same as 17 // 2)")
64 >> 1 = 32
64 >> 2 = 16
64 >> 3 = 8
64 >> 6 = 1

17 >> 1 = 8  (same as 17 // 2)

Right shifting is a common technique in bit manipulation algorithms where you need to extract individual bits of a number one at a time, starting from the most significant bit down.

Checking and Setting Specific Bits

One of the most practical applications of Python bitwise operators is reading and writing individual bits in an integer. This pattern is used in embedded systems, networking protocols, and anywhere compact binary data storage matters.

To check whether a specific bit at position i is set (equal to 1), you create a mask using the left shift operator and then AND it with the number.

def is_bit_set(number, position):
    mask = 1 << position
    return bool(number & mask)

def set_bit(number, position):
    mask = 1 << position
    return number | mask

def clear_bit(number, position):
    mask = ~(1 << position)
    return number & mask

def toggle_bit(number, position):
    mask = 1 << position
    return number ^ mask

value = 0b10110  # 22 in decimal

print(f"Value: {bin(value)} ({value})")
print(f"Bit 1 set? {is_bit_set(value, 1)}")
print(f"Bit 2 set? {is_bit_set(value, 2)}")
print(f"Bit 0 set? {is_bit_set(value, 0)}")

new_val = set_bit(value, 0)
print(f"\nAfter setting bit 0: {bin(new_val)} ({new_val})")

cleared = clear_bit(value, 2)
print(f"After clearing bit 2: {bin(cleared)} ({cleared})")

toggled = toggle_bit(value, 4)
print(f"After toggling bit 4: {bin(toggled)} ({toggled})")
Value: 0b10110 (22)
Bit 1 set? True
Bit 2 set? True
Bit 0 set? False

After setting bit 0: 0b10111 (23)
After clearing bit 2: 0b10010 (18)
After toggling bit 4: 0b110 (6)

These four operations — check, set, clear, toggle — form the building blocks of virtually all bit manipulation code you will encounter in the real world. Mastering them with Python bitwise operators gives you a toolkit that transfers directly to lower-level languages like C and C++ as well.

Operator Precedence with Bitwise Operators

Python bitwise operators have lower precedence than arithmetic operators but higher precedence than comparison operators. The NOT operator ~ has the highest precedence among bitwise operators, followed by shifts, then AND, then XOR, and finally OR.

Misunderstanding precedence is a common source of bugs, so it is always a good idea to use parentheses when mixing bitwise operators with other operators to make your intent explicit.

# Without parentheses — might be surprising
a = 2 + 3 & 5
b = (2 + 3) & 5   # This is what the line above actually computes
c = 2 + (3 & 5)   # This is different!

print(f"2 + 3 & 5 = {a}")
print(f"(2 + 3) & 5 = {b}")
print(f"2 + (3 & 5) = {c}")
2 + 3 & 5 = 5
(2 + 3) & 5 = 5
2 + (3 & 5) = 3

For a full reference on operator precedence in Python, check the official Python documentation on expressions.

Full Working Example

This example brings together all six Python bitwise operators in a small program that simulates a simple permission system and demonstrates bit-level operations on a set of values.

# Full working example: Python Bitwise Operators in action

# Define permission flags using left shift to place each in a unique bit position
READ    = 1 << 0   # 0001
WRITE   = 1 << 1   # 0010
EXECUTE = 1 << 2   # 0100
DELETE  = 1 << 3   # 1000

def grant(current, permission):
    return current | permission   # OR to set a bit

def revoke(current, permission):
    return current & ~permission  # AND NOT to clear a bit

def has_permission(current, permission):
    return bool(current & permission)  # AND to check a bit

def toggle_permission(current, permission):
    return current ^ permission   # XOR to flip a bit

def show_permissions(perm_val):
    print(f"  Raw value : {perm_val} ({bin(perm_val)})")
    print(f"  READ      : {has_permission(perm_val, READ)}")
    print(f"  WRITE     : {has_permission(perm_val, WRITE)}")
    print(f"  EXECUTE   : {has_permission(perm_val, EXECUTE)}")
    print(f"  DELETE    : {has_permission(perm_val, DELETE)}")

# Start with no permissions
user = 0
print("=== Starting with no permissions ===")
show_permissions(user)

# Grant READ and WRITE
user = grant(user, READ)
user = grant(user, WRITE)
print("\n=== After granting READ and WRITE ===")
show_permissions(user)

# Revoke WRITE
user = revoke(user, WRITE)
print("\n=== After revoking WRITE ===")
show_permissions(user)

# Toggle EXECUTE (turns it on since it was off)
user = toggle_permission(user, EXECUTE)
print("\n=== After toggling EXECUTE ===")
show_permissions(user)

# Demonstrate shift operators for powers of 2
print("\n=== Left shift: powers of 2 ===")
for i in range(5):
    print(f"  1 << {i} = {1 << i}")

# Demonstrate right shift for integer division by powers of 2
print("\n=== Right shift: fast division ===")
value = 256
for shift in [1, 2, 3, 4]:
    print(f"  {value} >> {shift} = {value >> shift}")

# Demonstrate NOT operator
print("\n=== Bitwise NOT ===")
for n in [0, 5, 127]:
    print(f"  ~{n} = {~n}")

# XOR trick: find the one unique element
print("\n=== XOR unique finder ===")
data = [4, 9, 4, 7, 9, 3, 7]
result = 0
for x in data:
    result ^= x
print(f"  List: {data}")
print(f"  Unique element: {result}")
=== Starting with no permissions ===
  Raw value : 0 (0b0)
  READ      : False
  WRITE     : False
  EXECUTE   : False
  DELETE    : False

=== After granting READ and WRITE ===
  Raw value : 3 (0b11)
  READ      : True
  WRITE     : True
  EXECUTE   : False
  DELETE    : False

=== After revoking WRITE ===
  Raw value : 1 (0b1)
  READ      : True
  WRITE     : False
  EXECUTE   : False
  DELETE    : False

=== After toggling EXECUTE ===
  Raw value : 5 (0b101)
  READ      : True
  WRITE     : False
  EXECUTE   : True
  DELETE    : False

=== Left shift: powers of 2 ===
  1 << 0 = 1
  1 << 1 = 2
  1 << 2 = 4
  1 << 3 = 8
  1 << 4 = 16

=== Right shift: fast division ===
  256 >> 1 = 128
  256 >> 2 = 64
  256 >> 3 = 32
  256 >> 4 = 16

=== Bitwise NOT ===
  ~0 = -1
  ~5 = -6
  ~127 = -128

=== XOR unique finder ===
  List: [4, 9, 4, 7, 9, 3, 7]
  Unique element: 3