Logical operators combine booleans into new booleans. Python has three: and, or, and not.
Other languages call these &&, ||, and !. Python uses words — easier to read aloud, harder to mistype.
and, or, not
age: int = 20
has_id: bool = True
# and — both sides must be True
print(age >= 18 and has_id) # True
# or — at least one side must be True
print(age < 18 or has_id) # True
# not — flip the value
print(not has_id) # False
Truth tables (quick reference)
A and B
True and True → True
True and False → False
False and True → False
False and False → False
A or B
True or True → True
True or False → True
False or True → True
False or False → False
not A
not True → False
not False → True
Combining many conditions
You can chain as many as you like. Use parentheses if order isn’t obvious:
age: int = 25
has_ticket: bool = True
is_member: bool = False
can_enter: bool = (age >= 18) and (has_ticket or is_member)
print(can_enter) # True
Without the parentheses, Python still gets it right (because and binds tighter than or), but the parens make your intent clear to a reader.
Short-circuit evaluation
Python evaluates and and or lazily, left to right, and stops as soon as the answer is known. This is called short-circuiting.
# and: if the first part is False, the second is never checked
False and (10 / 0) # returns False, no division-by-zero error
# or: if the first part is True, the second is never checked
True or (10 / 0) # returns True, no division-by-zero error
This isn’t just a curiosity — it’s a useful pattern:
name: str | None = get_name() # might return None
# safe — len(name) is only called when name is truthy
if name and len(name) > 0:
print(f"Hello, {name}")
Without short-circuiting, len(name) would explode when name is None.
and/or return values, not just True/False
This is the most surprising thing about Python’s logical operators. They don’t always return True or False — they return one of the original values:
print(0 or "default") # 'default' (0 is falsy → return second)
print(5 or "default") # 5 (5 is truthy → return first)
print("Hello" and "World") # 'World' (both truthy → return second)
print("" and "World") # '' (first is falsy → return first)
The rules:
a or breturnsaifais truthy, otherwiseb.a and breturnsaifais falsy, otherwiseb.
This gives you a one-line way to pick a default:
name: str = user_input or "Anonymous"
If user_input is an empty string (falsy), name becomes "Anonymous".
Pyright’s strict mode may flag this pattern when the resulting type is ambiguous. For typed code, the safer form is
name = user_input if user_input else "Anonymous"— same effect, clearer types.
Combining with comparisons
Comparisons produce booleans, and logical operators combine them. This is what if statements are made of:
score: int = 75
attendance: float = 0.8
passes: bool = score >= 50 and attendance >= 0.75
print(passes) # True
In Python, you can also chain comparisons directly — usually clearer than using and:
if 0 <= score <= 100: # nicer than score >= 0 and score <= 100
print("Valid score")
What’s next
You can combine booleans. Next, the assignment operators — shortcuts for “do an operation and store the result back”.