The Story That Explains Control Flow
Without it, a program would be a single straight line: instruction 1, instruction 2, instruction 3, done. Nothing interesting. Real programs need to decide — should I send this email or wait? Is the user logged in or a guest? Is this number positive, negative, or zero? Every decision is a fork, and every fork is written with if, elif, and else.
Master this trio and you master the shape of every program you'll ever write. Everything else — loops, functions, classes — is just organizing decisions. Control flow is where a dumb list of instructions becomes something that thinks.
The Simple if Statement
The simplest control-flow structure is a single if. You write a condition; if it's True, the indented block runs. If it's False, the block is skipped and the program continues below.
# ─── BASIC IF SYNTAX ────────────────────────────────────
age = 21
if age >= 18:
print("You can vote.")
print("You can sign a contract.")
print("Program continues here.")
# OUTPUT:
# You can vote.
# You can sign a contract.
# Program continues here.
The Anatomy of an if Statement
Unlike C or Java, Python uses indentation to define code blocks — not braces. Everything indented under an if belongs to it. Break the indentation and the meaning changes. Use 4 spaces (never mix tabs and spaces), and let your editor auto-indent for you.
Two Paths — if / else
Real decisions almost always have a "what if not?" branch. That's what else is for. Exactly one of the two blocks will run — never both, never neither.
# ─── if-else — EXACTLY ONE BRANCH RUNS ──────────────────
score = 75
if score >= 60:
print("Pass")
else:
print("Fail")
# OUTPUT: Pass
# ─── COMMON PATTERN: DEFAULTING VALUES ─────────────────
name = input("Your name: ")
if name:
print(f"Hello, {name}!")
else:
print("Hello, stranger!")
Many Paths — if / elif / else
Life rarely fits into two categories. A test score isn't just "pass or fail" — it's A, B, C, D, or F. That's where elif shines: a chain of decisions evaluated in order, top to bottom. Python checks each condition; the first one that's True runs its block, and Python skips the rest.
# ─── GRADE CLASSIFIER ───────────────────────────────────
score = 85
if score >= 90:
grade = "A"
elif score >= 80:
grade = "B"
elif score >= 70:
grade = "C"
elif score >= 60:
grade = "D"
else:
grade = "F"
print(f"Score: {score} → Grade: {grade}")
# OUTPUT: Score: 85 → Grade: B
Python evaluates conditions top to bottom and stops at the first True. In the grade example, score = 95 matches score >= 90 first, so it gets "A" and Python never checks score >= 80. If you accidentally reversed the order (checked 60 first), everyone passing would get "D". Rule of thumb: put stricter conditions higher.
Truthiness — What Counts as True in Python?
The condition after if doesn't have to be an obvious True or False. Python quietly converts any value to a boolean using rules called truthiness. This lets you write elegant checks like if name: or if items: without ever spelling out the comparison.
| Value | Type |
|---|---|
| False | bool |
| None | NoneType |
| 0, 0.0, 0j | numeric zero |
| "" | empty string |
| [] | empty list |
| () | empty tuple |
| {} | empty dict |
| set() | empty set |
| Value | Why |
|---|---|
| True | the constant itself |
| 1, -1, 42, 3.14 | any nonzero number |
| "hello" | non-empty string |
| " " | even a space is non-empty! |
| [0] | list with any items |
| {"a": 1} | non-empty dict |
| (0,) | tuple with any items |
| Basically: anything not in the falsy list | |
# ─── ELEGANT TRUTHINESS CHECKS ──────────────────────────
name = ""
if name: # "" is falsy → skip
print(f"Hi, {name}")
else:
print("No name given") # runs
items = []
if items: # empty list → False → skip
print("Processing items...")
else:
print("Nothing to do.") # runs
# ─── VERIFY WITH bool() ────────────────────────────────
print(bool(0)) # False
print(bool("")) # False
print(bool([])) # False
print(bool(None)) # False
print(bool(-1)) # True — nonzero
print(bool("False")) # True — the STRING is non-empty!
print(bool(" ")) # True — one space is non-empty!
print(bool([0])) # True — list has an item (even if the item is 0)
bool("False") is True — because "False" is a five-character string, not the boolean. If you read user input and check if answer:, typing anything at all counts as True, even the word "no". Convert explicitly: if answer.lower() == "yes":.
Building Conditions — Comparison Operators
The heart of any if is a condition, and conditions are built from comparison operators. They compare two values and return True or False.
| Operator | Meaning | Example | Result |
|---|---|---|---|
| == | Equal to | 5 == 5 | True |
| != | Not equal to | 5 != 3 | True |
| > | Greater than | 7 > 3 | True |
| < | Less than | 7 < 3 | False |
| >= | Greater or equal | 5 >= 5 | True |
| <= | Less or equal | 4 <= 5 | True |
| in | Membership | 3 in [1, 2, 3] | True |
| not in | Non-membership | "x" not in "abc" | True |
| is | Identity (same object) | a is None | use for None/True/False |
Chained Comparisons — Python's Sweet Trick
# ─── PYTHON LETS YOU CHAIN COMPARISONS ──────────────────
age = 25
# Instead of writing this:
if age >= 18 and age <= 65:
print("Working age")
# You can write this — cleaner and more mathematical:
if 18 <= age <= 65:
print("Working age") # same result!
# Chains work with any operators
if 0 < x < 100: # between 0 and 100 (exclusive)
print("in range")
# ─── USE == NOT = FOR COMPARISONS ──────────────────────
# if age = 18: → SyntaxError!
# if age == 18: → correct
# ─── DON'T CONFUSE == AND is ───────────────────────────
# Use == to compare VALUES
print("abc" == "abc") # True — same value
# Use 'is' ONLY for None, True, False
value = None
if value is None: # CORRECT idiom
print("no value")
Combining Conditions — and, or, not
Real-world decisions rarely depend on a single condition. Python's three logical operators let you combine them: and (both must be True), or (at least one must be True), and not (flip True↔False).
# ─── and ────────────────────────────────────────────────
age = 25
has_license = True
if age >= 18 and has_license:
print("Can drive")
# ─── or ─────────────────────────────────────────────────
day = "Saturday"
is_holiday = False
if day == "Saturday" or day == "Sunday" or is_holiday:
print("Day off!")
# CLEANER using 'in':
if day in ("Saturday", "Sunday") or is_holiday:
print("Day off!")
# ─── not ────────────────────────────────────────────────
logged_in = False
if not logged_in:
print("Please log in")
# ─── COMBINING ALL THREE ────────────────────────────────
if (age >= 18 and age < 65) or not is_holiday:
print("Working hours apply")
Short-Circuit Evaluation — A Free Feature
# ─── PYTHON STOPS EVALUATING AT THE FIRST DECISIVE VALUE ─
# and — if the first is False, the second is NEVER checked
user = None
if user and user.name == "Alice": # SAFE — user.name never accessed if user is None
print("Hi Alice")
# or — if the first is True, the second is NEVER checked
name = ""
display = name or "Anonymous" # "" is falsy → use default
print(display) # Anonymous
# This makes 'or' great for default values
port = config.get("port") or 8080 # fallback if missing
When mixing operators: comparisons happen first, then not, then and, then or. When in doubt, use parentheses to make intent explicit — (a > 0 and b > 0) or c > 0 reads better than relying on precedence memory.
Nested if — Decisions Inside Decisions
You can place an if inside another if. This is nesting, and it's useful when the second question only makes sense after the first has been answered.
# ─── NESTED IF — LOGIN + PERMISSIONS ────────────────────
logged_in = True
is_admin = False
if logged_in:
print("Welcome back!")
if is_admin:
print("Admin panel available")
else:
print("Standard user view")
else:
print("Please log in first")
# OUTPUT:
# Welcome back!
# Standard user view
When to Flatten Nested Conditions
Deep nesting is a code smell — it's hard to read and easy to get wrong. Often you can flatten nested conditions using and, or use guard clauses (early returns/exits) to reduce indentation.
| Code |
|---|
| if user: |
| if user.active: |
| if user.role == "admin": |
| grant_access() |
| Three levels of indentation just to grant access. |
| Code |
|---|
| if user and user.active and user.role == "admin": |
| grant_access() |
| One level, one condition, obvious intent. Uses short-circuit safely. |
# ─── GUARD CLAUSES — EARLY EXITS BEATS NESTING ──────────
# INSTEAD OF NESTING everything:
def process(order):
if order:
if order.paid:
if order.items:
ship(order)
else:
print("No items")
else:
print("Not paid")
else:
print("No order")
# USE GUARD CLAUSES to fail fast and stay flat:
def process(order):
if not order:
print("No order")
return
if not order.paid:
print("Not paid")
return
if not order.items:
print("No items")
return
ship(order) # the "happy path" is flat and clear
The Ternary Expression — if in One Line
For simple two-way decisions, Python offers a compact conditional expression (often called the ternary operator). It fits on one line and returns a value, so you can use it in assignments and function arguments.
# ─── FULL if-else ───────────────────────────────────────
age = 20
if age >= 18:
status = "adult"
else:
status = "minor"
# ─── SAME THING AS A TERNARY ────────────────────────────
status = "adult" if age >= 18 else "minor"
print(status) # adult
# ─── INLINE INSIDE OTHER EXPRESSIONS ────────────────────
print(f"You are an {'adult' if age >= 18 else 'minor'}.")
# ─── SAFE DIVISION ──────────────────────────────────────
count = 0
avg = total / count if count > 0 else 0
# ─── DEFAULT PARAMETER PATTERN ─────────────────────────
def greet(name=None):
display = name if name else "stranger"
print(f"Hello, {display}")
greet() # Hello, stranger
greet("Alice") # Hello, Alice
Don't nest ternaries or chain them with more than one condition. If you find yourself writing a if x else b if y else c, stop and use a proper if-elif-else — it will be far easier to read a week from now. The ternary is for quick two-way choices; anything more deserves the full statement.
The match Statement — Python's Modern Alternative
Since Python 3.10, there's a fourth control-flow tool called match. It's designed for cases where you compare one value against many possibilities — think of it as a supercharged switch statement. For simple value comparisons it's cleaner than a long elif chain, and it can also unpack complex data structures.
# ─── LONG elif CHAIN (works in every Python version) ────
def http_status(code):
if code == 200:
return "OK"
elif code == 301:
return "Moved"
elif code == 404:
return "Not Found"
elif code == 500:
return "Server Error"
else:
return "Unknown"
# ─── SAME THING WITH match (Python 3.10+) ──────────────
def http_status(code):
match code:
case 200:
return "OK"
case 301:
return "Moved"
case 404:
return "Not Found"
case 500:
return "Server Error"
case _: # the "default" case
return "Unknown"
# ─── MATCH CAN GROUP CASES WITH | ──────────────────────
def day_kind(day):
match day:
case "Saturday" | "Sunday":
return "weekend"
case "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday":
return "weekday"
case _:
return "unknown"
Use if / elif / else for ranges and complex boolean logic (score >= 90, a and b). Use match when you're comparing one value against a fixed set of possibilities, or destructuring data structures. For older codebases (Python 3.9 and below), match isn't available — stick with elif.
Common Traps and How to Avoid Them
Complete Practice Program — Smart Grader
# grader.py — every control-flow concept in one program
def letter_grade(score):
"""Convert numeric score to letter grade using elif chain."""
if score >= 90:
return "A"
elif score >= 80:
return "B"
elif score >= 70:
return "C"
elif score >= 60:
return "D"
else:
return "F"
def is_passing(score):
"""Ternary expression — one-line if/else."""
return "Passed" if score >= 60 else "Failed"
def honors_status(score, attendance):
"""Combine multiple conditions with 'and'."""
if score >= 95 and attendance >= 95:
return "Highest Honors"
elif score >= 90 and attendance >= 90:
return "Honors"
else:
return "Regular"
def validate(score):
"""Guard clauses — early exits."""
if not isinstance(score, (int, float)):
return "Error: score must be a number"
if score < 0 or score > 100:
return "Error: score out of range"
return None # no error
def analyze(student):
name, score, attendance = student
line = "─" * 45
print(f"{line}\n {name}")
# Guard clause — validate first
error = validate(score)
if error:
print(f" {error}")
return
grade = letter_grade(score)
status = is_passing(score)
honors = honors_status(score, attendance)
# Chained comparison
if 70 <= score <= 89:
note = "Solid effort — room to grow"
elif score >= 90:
note = "Excellent work!"
else:
note = "Needs improvement"
print(f" Score: {score}")
print(f" Grade: {grade}")
print(f" Status: {status}")
print(f" Attendance: {attendance}%")
print(f" Honors: {honors}")
print(f" Note: {note}")
def main():
students = [
("Alice", 96, 98),
("Bob", 72, 85),
("Carol", 58, 70),
("Dan", 88, 92),
("Erin", 150, 95), # invalid — guard catches this
]
for student in students:
analyze(student)
if __name__ == "__main__":
main()