Conditionals - advanced

💡 Big Idea

Simple conditionals ask one question at a time. But real decisions are rarely that tidy — you often need to check several things at once, or nest one decision inside another. This article introduces the tools that make complex logic possible: the <strong>and</strong>, <strong>or</strong>, and <strong>not</strong> operators, nested conditionals, and a practical technique called the guard clause. With these, you can express almost any decision a program needs to make.

🔍 Before You Read This Article

This article builds directly on Conditionals in Python. You should be comfortable with comparison operators (==, !=, >, <, >=, <=), the in operator, and the if / elif / else structure before continuing. If any of those feel shaky, revisit Article 3 first.

1   Boolean Operators: and, or, not

In Article 3, every condition was a single comparison: score >= 90, "@" in email. But conditions can be combined using three boolean operators that work just like their English equivalents.

OperatorWhat it doesResult is True when…Read it as…
andCombines two conditions — both must be true.Both sides are True."This and that must both be true."
orCombines two conditions — at least one must be true.Either side (or both) is True."This or that (or both) must be true."
notFlips a single condition — reverses its truth value.The original condition is False."This must not be true."

2   The and Operator

and requires all conditions to be true before the whole expression is true. Think of it as a series of gates — every gate must be open to pass through.

🔍 Analogy: Boarding a Flight

To board a plane you need a valid boarding pass and a valid passport. One is not enough — both must check out. If either one fails, you do not board. and works exactly this way: every condition must be true.

age = 20
has_ticket = True

if age >= 18 and has_ticket:
    print("You may board the flight.")
else:
    print("You cannot board.")

Python evaluates age >= 18 (True) and has_ticket (True). Both are true, so the whole condition is True and the first branch runs.

2.1   Truth Table for and

A truth table shows every possible combination of inputs and the resulting output. For and, the only way to get True is if both sides are True:

Condition ACondition BA and B
TrueTrueTrue
TrueFalseFalse
FalseTrueFalse
FalseFalseFalse

2.2   A Practical Example

username = "alex"
password = "python99"

entered_user = input("Username: ")
entered_pass = input("Password: ")

if entered_user == username and entered_pass == password:
    print("Login successful.")
else:
    print("Incorrect username or password.")

Notice that both conditions must pass — correct username and correct password. A correct username with the wrong password is still denied.

3   The or Operator

or requires at least one condition to be true. Even if only one side is true, the whole expression is true.

🔍 Analogy: A Fire Alarm

A fire alarm system triggers if it detects smoke or if it detects extreme heat. You do not need both — either one alone is enough to set off the alarm. or works the same way: if at least one condition is true, the result is true.

day = "Saturday"

if day == "Saturday" or day == "Sunday":
    print("It is the weekend — no school!")
else:
    print("It is a school day.")

3.1   Truth Table for or

The only way or gives False is when both sides are False:

Condition ACondition BA or B
TrueTrueTrue
TrueFalseTrue
FalseTrueTrue
FalseFalseFalse

3.2   A Practical Example

file_type = input("Enter file extension (without dot): ")

if file_type == "jpg" or file_type == "png" or file_type == "gif":
    print("This is an image file.")
else:
    print("This is not a recognised image format.")
💜 Cleaner alternative using in

When using or to check the same variable against several values, the in operator from Article 3 is often cleaner and easier to read:

if file_type in ["jpg", "png", "gif"]:
    print("This is an image file.")

Both approaches work — but the in version scales much better when you have many values to check. Choose whichever reads more naturally for the situation.

4   The not Operator

not flips a boolean value. True becomes False, and False becomes True. It applies to a single condition and reverses whatever it evaluates to.

🔍 Analogy: A Light Switch

not is a light switch for logic. If the light is on (True), flipping the switch turns it off (False). If it is off (False), flipping it turns it on (True). One flip, one reversal — every time.

4.1   Truth Table for not

Condition Anot A
TrueFalse
FalseTrue

4.2   Using not in Practice

is_logged_in = False

if not is_logged_in:
    print("Please log in to continue.")

# not also works with in:
blocked_users = ["spammer99", "troll42"]
username = "alex"

if username not in blocked_users:
    print("Access granted.")

You have already seen not in in Article 3 — that is actually the not operator combined with in. Python lets you write it as one readable phrase.

⚠️ not can make code harder to read

Double negatives are confusing in English ("I don't have no money") and just as confusing in code. if not is_not_valid: is hard to parse quickly. Whenever you reach for not, pause and ask: is there a positive way to write this instead? For example, if is_valid: is clearer than if not is_invalid:. Use not when it genuinely reads more naturally — not just as a reflex.

✅ Check Your Understanding
  1. Without running the code, predict the output of each of these:

    print(True and False)
    print(True or False)
    print(not True)
    print(False or False)
    print(True and True and False)
  2. A student writes this condition: if age > 12 and age > 17: to check if someone is a teenager (13–17). There is a logical error. What is it, and how do you fix it?
  3. Rewrite this chain of or comparisons using the in operator:

    if colour == "red" or colour == "blue" or colour == "yellow":
        print("Primary colour")
  4. What does not do to a boolean value? What is not (5 > 3)?
  5. A site wants to block access if the user is under 13 OR is on a banned list. Write the condition.

5   Combining Multiple Operators

You can chain and, or, and not together in one condition. This is where things get powerful — but also where logic bugs tend to hide.

5.1   Operator Precedence

Just like maths has an order of operations (multiplication before addition), Python has an order for boolean operators:

PriorityOperatorEvaluated…
1 (highest)notFirst
2andSecond
3 (lowest)orLast

This means not binds tightest, and comes next, and or is evaluated last. Consider this expression:

is_student = True
has_id = False
is_staff = True

# What does this evaluate to?
result = is_student and has_id or is_staff
print(result)    # True

Python reads this as (is_student and has_id) or is_staff — because and has higher priority than or. So it first evaluates True and FalseFalse, then False or TrueTrue.

5.2   Use Parentheses to Be Clear

Relying on operator precedence makes code hard to read and easy to get wrong. Use parentheses to make your intent explicit. Parentheses are always evaluated first — just like in maths.

# Ambiguous — relies on precedence rules
if is_student and has_id or is_staff:
    print("Entry allowed")

# Clear — parentheses show exactly what is grouped
if (is_student and has_id) or is_staff:
    print("Entry allowed — staff or verified student.")

# A different meaning entirely:
if is_student and (has_id or is_staff):
    print("Entry allowed — student with ID or staff status.")
💜 The parentheses rule

Whenever you combine and and or in the same condition, add parentheses — even if they are technically not required. They cost nothing, they prevent bugs, and they make your intent immediately clear to anyone reading the code — including future you.

✅ Check Your Understanding
  1. What is the order of precedence for not, and, or? Which is evaluated first?
  2. Evaluate this expression step by step. Show your working.

    x = 5
    y = 10
    z = 3
    result = x < y and not z > x or y == 10
    print(result)
  3. Add parentheses to make this condition check: "user must be an admin, OR be both a member and have a verified email":

    if is_admin or is_member and is_verified:
  4. Without parentheses, does True or False and False evaluate to True or False? Explain why, using the precedence rules.

6   Nested Conditionals

A nested conditional is an if statement placed inside another if statement. The inner condition only runs if the outer condition is already true — like a second gate that only appears after you pass the first.

🔍 Analogy: A Bouncer and a VIP List

Imagine a club with two checkpoints. At the door, a bouncer checks your age — you must be 18 or over to enter. If you pass that check, a second person checks whether you are on the VIP list. Only if you pass both checkpoints do you get VIP access. The second check does not even happen unless you passed the first. That is a nested conditional.

age = 20
is_vip = True

if age >= 18:
    print("Age check passed.")
    if is_vip:
        print("Welcome to the VIP area.")
    else:
        print("You may enter the general area.")
else:
    print("Entry denied — you must be 18 or older.")

Notice the indentation: the inner if/else is indented one level further in than the outer if. Python uses that indentation to know which block belongs to which conditional.

6.1   Tracing a Nested Conditional

Let's trace this program with two different inputs and see what happens:

InputOuter condition (age >= 18)Inner condition checked?Output
age=20, is_vip=TrueTrueYes — is_vip is True"Age check passed." then "Welcome to the VIP area."
age=20, is_vip=FalseTrueYes — is_vip is False"Age check passed." then "You may enter the general area."
age=15, is_vip=TrueFalseNo — inner block is skipped entirely"Entry denied — you must be 18 or older."

6.2   When to Use Nesting vs. and

Sometimes you can achieve the same result with nesting or with and. They are not always interchangeable — but knowing when each applies is important.

ApproachUse when…Example
andBoth conditions lead to the same single outcome.if age >= 18 and is_vip: print("VIP access")
Nested ifEach condition has its own separate outcome or message, or the second check only makes sense once the first has passed.Check age first; only then check VIP status, with different messages for each result.
⚠️ Don't nest too deeply

Nesting more than two levels deep — an if inside an if inside an if — makes code very hard to read and debug. If you find yourself three levels in, it is usually a sign to rethink the structure. The guard clause pattern in the next section is one of the most useful tools for keeping nesting shallow.

✅ Check Your Understanding
  1. What is a nested conditional? In plain English, how does the inner condition relate to the outer one?
  2. Trace this program for all four combinations of has_account and has_funds. What is printed in each case?

    has_account = True
    has_funds = False
    
    if has_account:
        if has_funds:
            print("Purchase complete.")
        else:
            print("Insufficient funds.")
    else:
        print("Please create an account first.")
  3. Could the program above be rewritten using and without nesting? Try it — but be careful: does the rewritten version produce the same messages for every input?
  4. Write a nested conditional for this scenario: if a student's score is 50 or above, they pass. If they pass and their score is 90 or above, also print "Distinction awarded."

7   Guard Clauses: Handling Problems First

A guard clause is a technique where you check for problem cases right at the top of a block of code and exit early — before doing any of the main work. It is one of the most useful habits in professional programming.

🔍 Analogy: Security Screening

At an airport, security checks happen at the entrance — not in the middle of the departure lounge. If you do not pass the check, you are stopped immediately and the rest of the airport never has to deal with you. Guard clauses do the same thing in code: catch problems at the start so the rest of your program can run cleanly, without worrying about bad inputs or invalid states.

7.1   Without a Guard Clause

Here is a program that calculates a student's grade — written without guard clauses. Notice how the "main" logic is buried inside nested conditionals:

score = int(input("Enter score: "))

if score >= 0:
    if score <= 100:
        if score >= 90:
            print("Grade: A")
        elif score >= 80:
            print("Grade: B")
        elif score >= 70:
            print("Grade: C")
        else:
            print("Grade: F")
    else:
        print("Error: score cannot be above 100.")
else:
    print("Error: score cannot be negative.")

This works — but the actual grading logic is buried two levels deep. Every time you read it, you have to mentally "unwrap" the nesting to find the main work.

7.2   With Guard Clauses

Now look at the same program written with guard clauses. The error cases are handled first, and the main logic sits flat — no nesting needed:

score = int(input("Enter score: "))

if score < 0:
    print("Error: score cannot be negative.")
elif score > 100:
    print("Error: score cannot be above 100.")
elif score >= 90:
    print("Grade: A")
elif score >= 80:
    print("Grade: B")
elif score >= 70:
    print("Grade: C")
else:
    print("Grade: F")

The logic is identical — but now there is no nesting at all. The invalid cases are dealt with immediately, and the grading runs cleanly in the remaining elifs.

💜 The guard clause mindset

Get into the habit of asking: "What are the things that should stop this code from running — invalid input, missing data, impossible values?" Handle those first. Then write your main logic knowing the bad cases are already gone. Your code will be flatter, cleaner, and easier to debug.

✅ Check Your Understanding
  1. In your own words, what is a guard clause and what problem does it solve?
  2. Rewrite this nested program using guard clauses so that no nesting is needed:

    name = input("Enter your name: ")
    if len(name) > 0:
        if len(name) <= 20:
            print("Hello,", name)
        else:
            print("Error: name is too long.")
    else:
        print("Error: name cannot be empty.")
  3. What is the advantage of handling error cases at the top of your code rather than at the bottom?

8   Putting It All Together

Here is a program that uses everything from this article — and from Article 3 — in one realistic scenario: a simple event ticket checker. Read it carefully, trace it for a few inputs, then answer the questions below.

VALID_CODES = ["GOLD2026", "SILVER2026", "PRESS2026"]
MIN_AGE = 16

name = input("Name: ")
age = int(input("Age: "))
code = input("Ticket code: ").upper()    # .upper() makes comparison case-insensitive

# Guard clauses — check for bad input first
if not name:
    print("Error: name cannot be empty.")
elif age < 0 or age > 120:
    print("Error: that age does not look right.")

# Main logic
elif age < MIN_AGE:
    print("Sorry,", name + ". You must be at least", MIN_AGE, "to attend.")
elif code not in VALID_CODES:
    print("Sorry,", name + ". That ticket code is not valid.")
else:
    # Passed all checks — now determine access level
    if code == "GOLD2026":
        print("Welcome,", name + "! You have GOLD access — enjoy the front row.")
    elif code == "PRESS2026":
        print("Welcome,", name + "! Press credentials confirmed.")
    else:
        print("Welcome,", name + "! Standard admission confirmed.")

Things to notice:

  • The first two elif blocks are guard clauses — they catch bad data before any real logic runs.
  • age < MIN_AGE and code not in VALID_CODES use a comparison and <strong>not in</strong> to filter out invalid cases.
  • age < 0 or age > 120 uses <strong>or</strong> to catch two different impossible values in one line.
  • The inner nested if/elif/else at the bottom only runs after all guard clauses have been cleared — it can safely assume the input is valid.
  • .upper() normalises the ticket code so "gold2026" and "GOLD2026" both work.

9   Final Check: The Big Picture

💡 Big Idea

and requires all conditions to be true. or requires at least one. not flips a boolean. When combining operators, not binds tightest, then and, then or — but use parentheses to make your intent clear. Nested conditionals place one decision inside another; use them when each outcome needs a different response. Guard clauses handle bad inputs first so the main logic stays clean and flat. Together, these tools let you express any decision your program needs to make.

✅ Final Check Your Understanding
  1. Fill in this combined truth table. Work through each row carefully.

    ABA and BA or Bnot Anot A and B
    TrueTrue????
    TrueFalse????
    FalseTrue????
    FalseFalse????
  2. Trace this program for the input score = 55 and attendance = 60. What is printed?

    MIN_SCORE = 50
    MIN_ATTENDANCE = 75
    
    if score < 0 or score > 100:
        print("Invalid score.")
    elif attendance < 0 or attendance > 100:
        print("Invalid attendance.")
    elif score >= MIN_SCORE and attendance >= MIN_ATTENDANCE:
        print("Pass — all criteria met.")
    elif score >= MIN_SCORE and attendance < MIN_ATTENDANCE:
        print("Conditional pass — attendance too low.")
    else:
        print("Fail.")
  3. A student writes this to check if a number is between 1 and 10 inclusive:

    if x > 1 and x < 10:

    This has a subtle bug. What is it? Write the correct condition.

  4. Explain the difference between these two conditions — when do they produce different results?

    if is_admin and is_active or is_superuser:
    
    if is_admin and (is_active or is_superuser):
  5. Write a program that asks for a username and a PIN (4-digit number). Use guard clauses to reject: an empty username, a PIN that is not exactly 4 digits long. If both pass, print "Access granted."

10   Quick-Reference Summary

ConceptSyntaxKey rule
andif a and b:Both conditions must be True.
orif a or b:At least one condition must be True.
notif not a:Flips True to False and vice versa.
PrecedencenotandorUse parentheses to override or clarify.
Nested conditionalif a:
    if b:
Inner block only runs if outer condition is True.
Guard clauseCheck bad cases first with if / elifHandle invalid inputs early; keep main logic flat.