Courses/Python Mastery/Module 4: Operators
Module 4 · Lesson 315 minBeginner

Logical Operators: and, or, not

Lesson goal
Combine conditions — and learn truthiness, Python's sneaky superpower.

Logical Operators: and, or, not

Real decisions combine conditions: *"if you're 18 and have a ticket"* — *"if it's raining or cloudy"* — *"if not logged in."* Three operators handle all of it.

The truth table (the whole lesson on one screen)

code
print(True and True)     # True
print(True and False)    # False
print(False and False)   # False

print(True or True)      # True
print(True or False)     # True
print(False or False)    # False

print(not True)          # False
print(not False)         # True

In words:

  • and — strict parent: *both* must be True
  • or — lenient parent: *at least one* True is enough
  • not — the rebel: flips whatever it sees
  • The real usage: combining comparisons

    code
    age = 20
    has_ticket = True
    
    # Both conditions must hold
    can_enter = age >= 18 and has_ticket
    print(can_enter)          # True
    
    # Any one is enough
    is_weekend = True
    is_holiday = False
    can_relax = is_weekend or is_holiday
    print(can_relax)          # True
    
    # Flip a flag
    is_raining = False
    need_umbrella = not is_raining
    print(need_umbrella)      # True... wait, that's wrong on purpose!

    That last one is a bug I planted — if it's NOT raining, do you need an umbrella? Read not is_raining out loud: "not raining" → True → but you need an umbrella when it IS raining. The correct line: need_umbrella = is_raining. Read every `not` out loud. It catches these instantly.

    Real conditions from real apps

    code
    # Login check
    username_ok = username == "admin"
    password_ok = password == "secret123"
    if username_ok and password_ok:
        print("Welcome!")
    
    # Range check (the readable way)
    if 0 < marks <= 100:
        print("Valid marks")
    
    # Discount eligibility
    if is_student or is_senior:
        price = price * 0.8
    
    # Guard clause
    if not is_logged_in:
        print("Please log in first")

    That last pattern — if not something: exit early — is called a guard clause, and professional code uses it everywhere.

    Truthiness — the sneaky superpower

    Python can treat ANY value as True or False:

    code
    # These are all "falsy" (act like False):
    print(bool(0))           # False
    print(bool(""))          # False — empty string
    print(bool([]))          # False — empty list
    print(bool(None))        # False
    
    # These are all "truthy" (act like True):
    print(bool(42))          # True
    print(bool("hi"))        # True — non-empty string
    print(bool([-1]))        # True — non-empty list

    Which enables beautifully short code:

    code
    name = input("Name: ")
    
    if name:                    # "if name is non-empty"
        print(f"Hello, {name}")
    else:
        print("You typed nothing!")

    if name: reads as "if there's a name" — Pythonic and clean.

    Short-circuit evaluation

    Python stops evaluating as soon as the answer is known:

    code
    print(False and crash_the_program)   # False — crash_the_program never runs!
    print(True or crash_the_program)     # True  — same, skipped

    and short-circuits on the first False; or short-circuits on the first True. Practical use — safe checks:

    code
    # If user is None, the second condition is never evaluated → no crash
    if user is not None and user.age >= 18:
        print("Adult user")

    Precedence: not > and > or

    code
    print(True or False and False)    # True — "and" binds tighter: True or (False and False)
    print((True or False) and False)  # False — parentheses change it

    Same advice as arithmetic: when mixing, use parentheses to say what you mean.

    Common Errors & Fixes

  • `and` where you meant `or` — read the condition aloud; "and" means BOTH.
  • `SyntaxError` on `if x > 5 and < 10:` — each side needs the variable: if x > 5 and x < 10: (or chain: 5 < x < 10).
  • Double negatives confusing youif not is_invalid: — consider renaming the flag is_valid and simplifying.

  • ✅ Checkpoint

  • True and not False? *(True — not False is True, then True and True)*
  • age < 18 or has_id — True if the user is 16 with no ID? *(False — both sides False)*
  • What's bool("")? *(False — empty string is falsy)*
  • Why does if user is not None and user.age >= 18 not crash when user is None? *(Short-circuit: the second check never runs)*
  • Next: assignment operators — the shorthand that makes updates one character shorter.