Skip to content

How to Use Logical and Bitwise AND Operators in Python

Learn the difference between Python's and (logical) and & (bitwise) operators, including how they work, what they return, and when to use each with regular values, Pandas, and NumPy.

Python provides two distinct AND operators that serve fundamentally different purposes: and for boolean logic and & for bitwise operations. They look similar and can sometimes produce the same result, which is exactly why confusing them leads to subtle bugs. This is especially common when working with data analysis libraries like Pandas and NumPy, where using the wrong operator raises confusing errors.

This guide explains how each operator works, what it returns, and when to use one over the other, with practical examples covering conditional logic, bit manipulation, and data filtering.

Understanding the Core Difference

The key distinction is what each operator evaluates:

  • and evaluates the truth value of entire objects or expressions. It works at the logical level.
  • & compares individual binary bits of integers. It works at the binary level.
Operator Type Operates On Returns
and Logical Truth values of whole objects First falsy value, or the last value
& Bitwise Individual bits of integers Integer result of bit-by-bit comparison

Using and for Boolean Logic

The and operator is what you use in everyday conditional logic. It evaluates expressions left to right and supports short-circuit evaluation, meaning Python stops as soon as the outcome is determined.

x = 10
y = 5

if x > 5 and y < 10:
    print("Both conditions are true")

Output:

Both conditions are true

Short-circuit evaluation

Short-circuiting means that if the first operand is falsy, Python immediately returns it without evaluating the second operand. This makes and ideal for guard conditions that protect against errors:

denominator = 0

if denominator != 0 and 100 / denominator > 5:
    print("Result is greater than 5")
else:
    print("Cannot divide or result too small")

Output:

Cannot divide or result too small

Because denominator != 0 is False, Python never evaluates 100 / denominator, avoiding a ZeroDivisionError.

The same pattern works for safely accessing attributes on objects that might be None:

user = None

if user and user.is_active:
    print("User is active")
else:
    print("No user or user is inactive")

Output:

No user or user is inactive

Return value behavior

A common misconception is that and always returns True or False. It actually returns the value that determined the result:

print(0 and "hello")
print("" and "world")
print(None and True)

Output:

0

None
print("hello" and "world")
print(1 and 2 and 3)

Output:

world
3

The logic is straightforward: if and finds a falsy value, it returns that value immediately (short-circuit). If every value is truthy, it returns the last one because that is the value that completed the evaluation.

Using & for Bitwise Operations

The & operator works at the binary level. It compares each pair of corresponding bits in two integers and produces a 1 only where both bits are 1:

a = 6   # Binary: 110
b = 3   # Binary: 011

result = a & b
print(result)

Output:

2

Checking if a number is even or odd

The least significant bit of any integer determines whether it is even (bit is 0) or odd (bit is 1). Using & 1 is a fast way to check:

number = 42
is_even = (number & 1) == 0
print(f"{number} is even: {is_even}")

number = 37
is_odd = (number & 1) == 1
print(f"{number} is odd: {is_odd}")

Output:

42 is even: True
37 is odd: True

Extracting bits with a mask

You can isolate specific bits from a value by ANDing it with a bitmask. Only the bits that are 1 in both the value and the mask survive:

value = 0b11010110
mask  = 0b00001111   # Keep only the lower 4 bits

lower_bits = value & mask
print(bin(lower_bits))

Output:

0b110

Permission flags

Bitwise AND is widely used in permission systems where each bit represents a specific capability:

READ_PERMISSION    = 0b100   # 4
WRITE_PERMISSION   = 0b010   # 2
EXECUTE_PERMISSION = 0b001   # 1

user_permissions = 0b101     # Read and execute, but not write

can_read = bool(user_permissions & READ_PERMISSION)
can_write = bool(user_permissions & WRITE_PERMISSION)
can_execute = bool(user_permissions & EXECUTE_PERMISSION)

print(f"Can read: {can_read}")
print(f"Can write: {can_write}")
print(f"Can execute: {can_execute}")

Output:

Can read: True
Can write: False
Can execute: True

The Pandas and NumPy Exception

This is where the distinction between and and & becomes critical in practice. When filtering arrays or DataFrames, you must use &, not and. These libraries override the & operator to perform element-wise comparisons across entire arrays or Series.

Why and fails with DataFrames

import pandas as pd

df = pd.DataFrame({
    "name": ["Alice", "Bob", "Charlie", "Diana"],
    "age": [25, 30, 35, 28],
    "salary": [50000, 60000, 75000, 55000],
})

filtered = df[df["age"] > 25 and df["salary"] < 70000]

Output:

ValueError: The truth value of a Series is ambiguous.
Use a.empty, a.bool(), a.item(), a.any() or a.all().

The error occurs because and tries to evaluate the truth value of the entire Series df["age"] > 25 as a single boolean. A Series containing multiple values cannot be reduced to a single True or False, so Python raises a ValueError.

The correct approach with &

import pandas as pd

df = pd.DataFrame({
    "name": ["Alice", "Bob", "Charlie", "Diana"],
    "age": [25, 30, 35, 28],
    "salary": [50000, 60000, 75000, 55000],
})

filtered = df[(df["age"] > 25) & (df["salary"] < 70000)]
print(filtered)

Output:

    name  age  salary
1    Bob   30   60000
3  Diana   28   55000

NumPy array filtering

The same rules apply to NumPy arrays:

import numpy as np

arr = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])

result = arr[(arr > 3) & (arr < 8)]
print(result)

Output:

[4 5 6 7]

Combining multiple conditions in Pandas

You can chain as many conditions as needed with &. For readability, place each condition on its own line:

import pandas as pd

df = pd.DataFrame({
    "product": ["A", "B", "C", "D", "E"],
    "price": [100, 250, 175, 300, 125],
    "stock": [50, 0, 30, 10, 0],
})

available_midrange = df[
    (df["price"] >= 100)
    & (df["price"] <= 200)
    & (df["stock"] > 0)
]
print(available_midrange)

Output:

  product  price  stock
0       A    100     50
2       C    175     30

Common Mistake: Mixing Up and and &

Using & where you meant and in regular Python code can produce results that look correct for some inputs but are silently wrong for others:

x = 3
y = 5

print(x > 0 and y > 0)   # True (correct: boolean logic)
print((x > 0) & (y > 0)) # True (happens to work, but semantically wrong)

a = 6   # Binary: 110
b = 3   # Binary: 011

print(a and b)  # 3 (returns last truthy value)
print(a & b)    # 2 (bitwise AND: 110 & 011 = 010)

Output:

True
True
3
2

For a and b, both 6 and 3 are truthy, so and returns the last value: 3. For a & b, the bits are compared individually, producing 2. These are completely different operations that happen to both return truthy values here, making the bug easy to miss.

Quick Reference

Scenario Operator Example
if statements and if x > 0 and y > 0:
Guard / safety checks and if obj and obj.valid:
Bit manipulation & flags & MASK
NumPy array filtering & arr[(arr > 0) & (arr < 10)]
Pandas DataFrame filtering & df[(df.a > 1) & (df.b < 5)]

Summary

Python's two AND operators serve fundamentally different purposes:

  • Use and for everyday boolean logic in if statements, guard conditions, and any situation where you are evaluating the truthiness of whole expressions. It short-circuits and returns the actual value that determined the result.
  • Use & for bitwise operations on integers, such as checking flags, applying masks, or testing even/odd. It compares individual bits.
  • In Pandas and NumPy, always use & (with parentheses around each condition) for element-wise filtering. Using and will raise a ValueError because these libraries cannot reduce an entire array or Series to a single boolean value.
  • When in doubt, remember: and answers "are both of these things true?" while & answers "which bits are set in both of these numbers?"
Navigation