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.
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.
| Operator | What it does | Result is True when… | Read it as… |
|---|---|---|---|
and | Combines two conditions — both must be true. | Both sides are True. | "This and that must both be true." |
or | Combines two conditions — at least one must be true. | Either side (or both) is True. | "This or that (or both) must be true." |
not | Flips 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.
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 A | Condition B | A and B |
|---|---|---|
| True | True | True |
| True | False | False |
| False | True | False |
| False | False | False |
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.
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 A | Condition B | A or B |
|---|---|---|
| True | True | True |
| True | False | True |
| False | True | True |
| False | False | False |
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.")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.
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 A | not A |
|---|---|
| True | False |
| False | True |
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.
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.
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)
- 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? Rewrite this chain of
orcomparisons using theinoperator:if colour == "red" or colour == "blue" or colour == "yellow": print("Primary colour")- What does
notdo to a boolean value? What isnot (5 > 3)? - 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:
| Priority | Operator | Evaluated… |
|---|---|---|
| 1 (highest) | not | First |
| 2 | and | Second |
| 3 (lowest) | or | Last |
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 False → False, then False or True → True.
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.")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.
- What is the order of precedence for
not,and,or? Which is evaluated first? 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)
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:
- Without parentheses, does
True or False and Falseevaluate toTrueorFalse? 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.
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:
| Input | Outer condition (age >= 18) | Inner condition checked? | Output |
|---|---|---|---|
age=20, is_vip=True | True | Yes — is_vip is True | "Age check passed." then "Welcome to the VIP area." |
age=20, is_vip=False | True | Yes — is_vip is False | "Age check passed." then "You may enter the general area." |
age=15, is_vip=True | False | No — 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.
| Approach | Use when… | Example |
|---|---|---|
and | Both conditions lead to the same single outcome. | if age >= 18 and is_vip: print("VIP access") |
Nested if | Each 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. |
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.
- What is a nested conditional? In plain English, how does the inner condition relate to the outer one?
Trace this program for all four combinations of
has_accountandhas_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.")- Could the program above be rewritten using
andwithout nesting? Try it — but be careful: does the rewritten version produce the same messages for every input? - 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.
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.
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.
- In your own words, what is a guard clause and what problem does it solve?
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.")- 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
elifblocks are guard clauses — they catch bad data before any real logic runs. age < MIN_AGEandcode not in VALID_CODESuse a comparison and<strong>not in</strong>to filter out invalid cases.age < 0 or age > 120uses<strong>or</strong>to catch two different impossible values in one line.- The inner nested
if/elif/elseat 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
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.
Fill in this combined truth table. Work through each row carefully.
A B A and B A or B not A not A and B True True ? ? ? ? True False ? ? ? ? False True ? ? ? ? False False ? ? ? ? Trace this program for the input
score = 55andattendance = 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.")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.
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):
- 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
| Concept | Syntax | Key rule |
|---|---|---|
and | if a and b: | Both conditions must be True. |
or | if a or b: | At least one condition must be True. |
not | if not a: | Flips True to False and vice versa. |
| Precedence | not → and → or | Use parentheses to override or clarify. |
| Nested conditional | if a: if b: | Inner block only runs if outer condition is True. |
| Guard clause | Check bad cases first with if / elif | Handle invalid inputs early; keep main logic flat. |