Python and vs &: Logical and Bitwise Operators
python and vs &: Explains the difference between Python's `and` and `&` operators, covering precedence, short-circuiting, and common mistakes.
Understanding the difference between Python's and and & operators starts with their syntax and evaluation behavior. When you write a and b, you are using the logical AND operator. When you write a & b, you are using the bitwise AND operator. The two behave very differently, and confusing them leads to subtle bugs that are hard to trace. This article explains the core differences, how evaluation order affects your code, and where each operator belongs.
The Core Difference Between and and &
The logical and evaluates operands in a boolean context. It returns the first falsy operand if any exists, otherwise it returns the last operand. This means and does not always return True or False; it returns one of the original values. For example:
result = 0 and 10 # 0, because 0 is falsy result = 2 and 3 # 3, because both are truthy and 3 is last
The bitwise & operates on the binary representations of integers. It compares each bit position and returns an integer where a bit is set only if both corresponding bits are set. For example:
result = 5 & 3 # 1, because 5 (101) & 3 (011) = 001
If you apply & to booleans, Python treats True as 1 and False as 0, so the operation follows integer bitwise behavior rather than Boolean logic. Since bool is a subclass of int, True & False evaluates to 0, and True & True evaluates to 1; neither result is a bool object. This is a common source of confusion.
Operator Precedence and Evaluation Order
& has higher precedence than and. In an expression without parentheses, Python evaluates & before and. Consider:
x = 1 or 2 & 3 # 1, because 2 & 3 = 2, then 1 or 2 = 1
& also binds tighter than comparison operators such as == and !=. Because of that, x & y == z is parsed as (x & y) == z. If you want to test y == z before the bitwise AND, you must write x & (y == z). Relying on precedence is fragile because readers may not immediately know that & binds tighter. For example, a and b & c is parsed as a and (b & c), which is probably not what you intended if you wanted (a and b) & c. When you mix bitwise and logical operations, parenthesize the intended grouping.
Short-Circuiting Behavior
The logical and short-circuits. If the left operand is falsy, Python does not evaluate the right operand at all. This is important for performance and for avoiding errors when the right operand has side effects or depends on the left operand:
def divide(a, b): return b != 0 and a / b # avoids ZeroDivisionError
The bitwise & does not short-circuit. Both operands are fully evaluated before the bitwise operation runs. If you use & in a condition where you expect short-circuiting, you may trigger unnecessary work or exceptions. For example, (x != 0) & (10 / x > 1) will evaluate 10 / x even if x is zero, raising an error.
Using & on Booleans
Because & on booleans returns an integer rather than a bool, using it as a logical operator can create surprising type-dependent behavior. For example:
type(True & False) # <class 'int'>
If you need a boolean result, use and or convert explicitly with bool(). Using & for logical conditions works in many if statements because 0 is falsy and 1 is truthy, but it fails when you need short-circuiting or when you rely on the return type.
Common Mistakes and Pitfalls
The most frequent mistake is using & where and is intended, especially when porting code from languages like C or JavaScript where && is the logical operator. In Python, & is not a synonym for and. Consider a typical condition:
if is_admin & user_active: # This works if both are booleans, but it does not short-circuit # and it returns an int, not a bool
This works for simple boolean variables, but if user_active is a property or a function call, it is always evaluated, even when is_admin is False. That can cause performance problems or unexpected side effects.
Another pitfall is using & with lists or other iterables. Python does not support bitwise AND on lists; you get a TypeError. If you see code like list1 & list2, the developer likely meant set intersection or a logical AND over elements, but neither works with & directly.
Readability and Maintainability Considerations
Using and for logical conditions makes the intent explicit. The reader immediately knows that the expression is a boolean test. Using & for bitwise manipulation is appropriate when you are working with flags, masks, or binary protocols. Mixing them without clear separation makes code hard to read and maintain.
A practical guideline is to reserve & for operations where the operands are integers and the result is an integer. If you are working with booleans and want a logical AND, always use and. This keeps the code predictable and avoids surprising behavior when operands change type later.
When to Use Each Operator
Use and when you need logical conjunction, short-circuiting, or when the operands are arbitrary objects and you want to return one of them. Use & when you are performing bitwise operations on integers, such as extracting flags, masking values, or implementing binary algorithms.
For example, to check if a number is even, number & 1 == 0 is a common bitwise trick, but number % 2 == 0 is more readable. Use & only when the bitwise nature is essential to the algorithm, such as when working with hardware registers or network protocols.
If you find yourself writing a & b where both a and b are booleans, stop and reconsider. The logical and is almost always the correct choice because it short-circuits and returns a boolean. The only exception is when you deliberately need to avoid short-circuiting, but that is rare and usually a code smell.
Precedence Traps with Comparisons
Precedence can confuse readers even when the parse is correct. In Python, & has higher precedence than == and !=, so x & y == z is parsed as (x & y) == z. That is usually what you want when testing bit flags. If you mean x & (y == z), you must add parentheses. The danger with flags & 0x01 == 0x01 is not that Python misparses it; it parses as (flags & 0x01) == 0x01. The problem is that the intent is easy to misread. Adding parentheses around the bitwise operation makes the intent explicit:
if (flags & 0x01) == 0x01: # Correct and clearer
Always wrap bitwise operations in parentheses when they appear alongside comparisons or logical operators. This simple rule improves readability and prevents a whole class of precedence-related bugs.
Final Code Example: Correct Mixing
Here is a realistic example that uses both operators correctly:
class Permission: READ = 0x01 WRITE = 0x02 EXECUTE = 0x04 permission = Permission.READ | Permission.WRITE # bitwise OR if (permission & Permission.READ) and (permission & Permission.WRITE): print("Can read and write")
The & expressions extract individual bits, while and combines the two boolean checks. The parentheses are not strictly required because & binds tighter than and, but they make the grouping explicit and help avoid mistakes as the expression grows.
Understanding the difference between and and & is not just about memorizing syntax; it is about knowing which operator matches the data type and evaluation semantics you need. When in doubt, prefer and for logic and & for bitwise work, and always parenthesize mixed expressions.