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 b returns a if a is truthy, otherwise b.
  • a and b returns a if a is falsy, otherwise b.

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”.

Toggle theme (T)