DevHireLab
Tutorials
BootcampProblemsCode SimulatorAI InterviewSoonContact
DevHireLab
Tutorials
BootcampProblemsCode SimulatorAI InterviewSoonContact
Getting Started
01
What is Python and Why Learn It in 2026
02
Installing Python on Windows, Mac, and Linux
03
Setting up VS Code / PyCharm for Python
04
Understanding the Python Interpreter and REPL
05
Writing and Running Your First Python Program
06
Python Syntax and Indentation Rules
Variables and Data Types
01
Variables and Naming Conventions in Python
02
Numbers in Python (int, float, complex)
03
Strings and String Methods in Python
04
Booleans in Python
05
Type Conversion / Casting in Python
06
Understanding the None Type in Python
Operators
01
Arithmetic Operators in Python
02
Comparison Operators in Python
03
Logical Operators in Python
04
Assignment Operators in Python
05
Bitwise Operators in Python
06
Identity (is) and Membership (in) Operators
07
Walrus Operator (:=) in Python
Input, Output & Strings
01
input() and print() Functions in Python
02
f-strings and String Formatting in Python
03
String Slicing and Indexing in Python
04
Common String Methods (split, join, strip, replace)
05
Escape Characters in Python Strings
Control Flow
01
if, elif, else Statements in Python
02
Nested Conditionals in Python
03
for Loops in Python
04
while Loops in Python
05
break, continue, and pass Statements
06
Loop with else Clause in Python
07
match-case (Structural Pattern Matching)
Data Structures
01
Python Lists: Creation, Indexing, Slicing, Methods
02
Python Tuples and Immutability
03
Python Sets and Set Operations
04
Python Dictionaries: Keys, Values, and Methods
05
List Comprehensions in Python
06
Dictionary & Set Comprehensions in Python
07
Working with Nested Lists and Dictionaries
Functions
01
Defining and Calling Functions in Python
02
Function Arguments: Positional, Keyword, Default
03
*args and **kwargs in Python
04
Return Statements in Python Functions
05
Lambda Functions in Python
06
Recursion in Python
07
Variable Scope: Local, Global, Nonlocal
08
Docstrings and Function Annotations
Functional Programming Basics
01
map(), filter(), and reduce() in Python
02
Closures in Python Explained
03
Python Decorators: Basic to Advanced
04
Generators and yield in Python
Object-Oriented Programming (OOP)
01
Classes and Objects in Python
02
The init Constructor in Python
03
Instance vs Class Variables in Python
04
Inheritance in Python (Single, Multiple, Multilevel)
05
Polymorphism in Python
06
Encapsulation in Python
07
Abstraction in Python with the abc Module
08
Magic/Dunder Methods in Python
09
Static and Class Methods in Python
10
The @property Decorator in Python
11
Python Dataclasses Explained
Error Handling
01
try, except, finally in Python
02
Handling Multiple Exceptions in Python
03
Raising Custom Exceptions in Python
04
Understanding Python's Exception Hierarchy
05
Assertions in Python
File Handling
01
Reading and Writing Text Files in Python
02
Working with the with Statement in Python
03
Working with CSV Files in Python
04
Working with JSON Files in Python
05
Working with Directories (os, pathlib) in Python
Modules and Packages
01
Importing Built-in Modules in Python
02
Creating Your Own Modules in Python
03
Understanding Packages and init.py
04
Python Standard Library Overview
05
Installing Packages with pip
Advanced Core Concepts
01
Iterators and Iterables in Python
02
Context Managers in Python (with, contextlib)
03
Multithreading Basics in Python
04
Multiprocessing Basics in Python
05
Asyncio Basics: async/await in Python
06
Memory Management and Garbage Collection in Python
07
Type Hints in Python (typing module)

Bitwise Operators in Python

Most everyday Python code never touches individual bits directly — but python bitwise operators show up more often than you'd expect, from permission flags to performance tricks to how Python's own set operations are named. This article covers all six, including why one of them produces results that look confusingly negative.

What Are Bitwise Operators?

Bitwise operators work directly on the individual bits of an integer's binary representation, rather than on the number as a whole. This is fundamentally different from the logical operators covered in an earlier article — and, or, and not work with entire Boolean truth values (True/False), while bitwise operators work bit by bit, on the underlying binary digits that make up a number.

Python has six bitwise operators:

&    # AND
|    # OR
^    # XOR
~    # NOT
<<   # left shift
>>   # right shift

To make sense of any of these, it helps to remember that every integer is stored internally as a sequence of binary digits (bits) — 5, for instance, is 101 in binary. Bitwise operators compare or shift those digits directly.

Bitwise AND, OR, and XOR

& (AND): 1 only if both bits are 1
print(5 & 3)   # 1

Working through it in binary:

5 = 101
3 = 011
    ---
    001  (only the rightmost bit is 1 in both)

& compares each corresponding pair of bits — the result bit is 1 only where both inputs have a 1. This makes & useful for masking: isolating specific bits you care about while zeroing out the rest.

| (OR): 1 if either bit is 1
print(5 | 3)   # 7
5 = 101
3 = 011
    ---
    111  (a bit is 1 if it's 1 in either number)

| is commonly used to combine flags — setting multiple binary options at once without disturbing bits that are already set.

^ (XOR): 1 if exactly one bit is 1
print(5 ^ 3)   # 6
5 = 101
3 = 011
    ---
    110  (1 only where the bits differ)

XOR (exclusive or) is the one people find least intuitive at first. It returns 1 only when the two bits differ — if both bits are the same (both 0 or both 1), the result is 0. This gives XOR some genuinely useful properties: it's the basis for toggling a bit on and off with the same operation, for simple (non-cryptographically-secure) encryption schemes, and for quickly finding what's different between two values.

Bitwise NOT (~)

~ inverts every bit in a number — every 0 becomes a 1, and every 1 becomes a 0. This is sometimes called the "one's complement."

print(~5)   # -6
Why the result looks negative

This is the part that confuses almost everyone the first time they see it. Python (like most languages) represents integers using a system called two's complement, where the leftmost bit effectively signals whether a number is negative. Flipping every bit of a positive number necessarily flips that sign bit too, which is why ~5 doesn't give you some large unsigned binary value — it gives you a negative number instead.

There's a simple formula that captures this behavior without needing to think through the binary representation each time:

# ~x is always equivalent to -x - 1
print(~5)    # -6  →  -(5) - 1
print(~0)    # -1  →  -(0) - 1
print(~-1)   # 0   →  -(-1) - 1

That formula — ~x == -x - 1 — is worth just memorizing, since it explains the output far faster than mentally flipping bits.

Left and Right Shift Operators (<<, >>)

Left shift (<<)

<< shifts a number's bits to the left by a given number of positions, filling in zeros on the right. Each shift to the left is mathematically equivalent to multiplying by 2, raised to the power of however many positions you shifted:

print(4 << 1)   # 8   — 4 * (2 ** 1)
print(4 << 2)   # 16  — 4 * (2 ** 2)
print(1 << 3)   # 8   — 1 * (2 ** 3)
Right shift (>>)

>> does the reverse — it shifts bits to the right, which is equivalent to floor-dividing by 2 raised to the power of the shift amount:

print(16 >> 1)   # 8   — 16 // (2 ** 1)
print(16 >> 2)   # 4   — 16 // (2 ** 2)
print(9 >> 1)     # 4   — 9 // 2, floor division discards the remainder
Practical example: fast multiplication and division by powers of two

Because these operators map directly to multiplying or dividing by powers of two, they're occasionally used as a performance shortcut in code where every cycle matters — particularly in lower-level or performance-sensitive contexts:

value = 10

doubled = value << 1     # equivalent to value * 2
halved = value >> 1      # equivalent to value // 2

print(doubled, halved)   # 20 5

In typical, everyday Python code, this micro-optimization rarely matters — write value * 2 for clarity unless you have a specific, measured reason to reach for the shift operator instead.

Real-World Uses and Best Practices

Common applications

Bitwise operators show up in a handful of recognizable situations:

  • Bit masking — isolating or clearing specific bits within a larger value, common in low-level data parsing.

  • Flags and permissions — representing several independent on/off options as a single integer, where each bit represents one flag. This pattern appears in file permission systems and various configuration formats.

  • Low-level system programming — networking code, hardware interfaces, and file format parsing frequently need to manipulate individual bits directly.

  • Simple XOR-based encryption — XOR's "toggle" property makes it a building block in basic obfuscation or encryption schemes, though modern cryptography relies on far more sophisticated algorithms for anything security-sensitive.

Using bin() to visualize binary

While you're learning, Python's built-in bin() function is genuinely useful for seeing exactly what's happening at the bit level, rather than working it out by hand:

print(bin(5))    # 0b101
print(bin(3))    # 0b11
print(bin(5 & 3))   # 0b1
print(bin(5 | 3))   # 0b111

Running your bitwise expressions through bin() while you're first getting comfortable with these operators makes the binary math tangible instead of abstract.

Operator precedence

Bitwise operators bind lower than most arithmetic operators in Python — meaning arithmetic gets evaluated before bitwise operations, unless you specify otherwise. This can produce results that don't match what you'd naturally assume:

result = 1 << 2 + 3
print(result)   # 32 — not what you might expect

# What actually happened: addition ran first, then the shift
# 1 << (2 + 3)  →  1 << 5  →  32

To avoid this kind of surprise, wrap bitwise operations in explicit parentheses any time they're mixed with arithmetic:

result = (1 << 2) + 3
print(result)   # 7 — the shift happens first, as intended
A note on operator overloading

Python allows custom classes to define their own behavior for these operators through special "dunder" methods — __and__() for &, __or__() for |, __xor__() for ^, and so on. This is a more advanced topic you'll encounter later when working with classes, but there's a concrete example of it you've likely already used without realizing it: Python's built-in set type reuses &, |, and ^ for set operations — intersection, union, and symmetric difference, respectively:

a = {1, 2, 3}
b = {2, 3, 4}

print(a & b)   # {2, 3} — intersection
print(a | b)   # {1, 2, 3, 4} — union
print(a ^ b)   # {1, 4} — symmetric difference (in one set or the other, not both)

This isn't a coincidence — it's a deliberate example of operator overloading in Python's own standard library, showing how the same symbols can mean something entirely different depending on the type of the operands involved.

PREVIOUSNEXT LESSON