Python control flow uses indentation to define blocks — if/elif/else for branching, for to iterate any iterable, while for condition-based loops, and match/case (Python 3.10+) for structural pattern matching.
Making decisions and repeating actions
What you'll learn: How to write programs that make decisions (if/elif/else) and repeat actions (for and while loops). After this tab, you can write programs that do different things based on different inputs — which is what most programs actually do.
How to read this tab: Work through each section with the code examples open. For each code block, read the comments first, predict the output, then read the actual output. The prediction step is what turns reading into understanding.
Control flow is the signposts on a road — they tell your code which way to go, how many times to go around the roundabout, and when to take the exit. Without them, code runs straight from top to bottom and never makes a decision.
What this section is: The most fundamental decision-making tool in programming. Python uses indentation (4 spaces) to define what's inside an if block — there are no curly braces like other languages. The key word here is truthy: Python evaluates conditions as True/False based on value, not just the boolean type.
if, elif, else
score = 85
if score >= 90:
grade = "A"
elif score >= 80:
grade = "B"
elif score >= 70:
grade = "C"
else:
grade = "F"
print(grade) # B
# Ternary expression (conditional expression)
status = "pass" if score >= 50 else "fail"
# Truthy and falsy values
# Falsy: False, None, 0, 0.0, "", [], {}, set()
# Everything else is truthy
name = ""
if not name:
print("Name is empty") # prints this
# Chained comparisons — Python supports them natively
x = 5
if 0 < x < 10:
print("x is between 0 and 10") # prints
# match/case — structural pattern matching (Python 3.10+)
command = "quit"
match command:
case "quit" | "exit" | "q":
print("Goodbye!")
case "help":
print("Available commands: quit, help")
case _:
print(f"Unknown command: {command}")This is the loop you'll use 90% of the time. Python's for loop iterates over any sequence — a list, a string, a range, a dict. The pattern for item in collection: is everywhere in Python code. Get very comfortable with this before moving on.
for loops
Python's for loop iterates over any iterable — a list, string, range, dictionary, file, generator, or any object that implements __iter__. There is no C-style for (i=0; i<n; i++). To iterate with an index, use enumerate().
# Iterate over any iterable
fruits = ["mango", "apple", "banana"]
for fruit in fruits:
print(fruit)
# Range — generates integers on demand
for i in range(5): # 0, 1, 2, 3, 4
print(i)
for i in range(2, 10, 2): # start, stop, step — 2,4,6,8
print(i)
# enumerate — index + value together
for i, fruit in enumerate(fruits):
print(f"{i}: {fruit}")
# zip — iterate multiple sequences together
names = ["Priya", "Rahul", "Anita"]
scores = [95, 88, 72]
for name, score in zip(names, scores):
print(f"{name}: {score}")
# dict iteration
config = {"host": "localhost", "port": 5432}
for key, value in config.items():
print(f"{key} = {value}")
# List comprehension — functional for loop
squares = [x**2 for x in range(1, 6)]
evens = [x for x in range(20) if x % 2 == 0]
# Nested
matrix = [[i*j for j in range(1,4)] for i in range(1,4)]Infinite loop warning. A while loop runs forever if its condition never becomes False. Always make sure something inside the loop changes the condition, or use a break statement. An infinite loop in the wrong place will freeze your program.
while loops and loop control
# while — repeat while condition is true
count = 0
while count < 5:
print(count)
count += 1
# break — exit the loop immediately
for n in range(100):
if n * n > 50:
print(f"First n where n² > 50: {n}")
break
# continue — skip to next iteration
for n in range(10):
if n % 2 == 0:
continue # skip evens
print(n) # prints 1,3,5,7,9
# else on loops — runs if loop completed without break
for n in range(2, 10):
for divisor in range(2, n):
if n % divisor == 0:
break
else:
print(f"{n} is prime") # only runs if inner loop didn't break
# pass — do nothing (syntactic placeholder)
def not_yet_implemented():
pass # valid empty function body✅ Beginner tab complete — check your understanding
- I can write an if/elif/else block that handles at least 3 conditions
- I can write a for loop that iterates over a list and does something with each item
- I can write a while loop that runs until a condition becomes False
- I know what break and continue do inside a loop
- I understand what "truthy" and "falsy" mean in Python
Iterators, pattern matching, and boolean evaluation
What you'll learn: How Python's for loop actually works under the hood (the iterator protocol), the match/case statement from Python 3.10, and the precise rules Python uses to evaluate boolean expressions.
How to read this tab: Read after you've written some code using the Beginner tab concepts. The iterator protocol section especially makes much more sense after you've used for loops in practice.
What this section is: The mechanism that makes for loops work — every for loop calls __iter__() then __next__() repeatedly. You don't need to implement this yourself yet, but understanding it explains why for loops work on lists, strings, files, generators, and anything else that implements the protocol.
Iterators and the iteration protocol
A for loop in Python is syntactic sugar for the iterator protocol: Python calls iter(obj) to get an iterator, then repeatedly calls next(iterator) until StopIteration is raised. Any object implementing __iter__ and __next__ is an iterator. This protocol is what makes generators, file objects, ranges, and custom classes all work with for.
# Under the hood of "for x in lst:"
lst = [1, 2, 3]
iterator = iter(lst) # calls lst.__iter__()
print(next(iterator)) # 1 — calls iterator.__next__()
print(next(iterator)) # 2
print(next(iterator)) # 3
# next(iterator) # StopIteration
# Custom iterable class
class Countdown:
def __init__(self, start):
self.start = start
def __iter__(self):
return self # this object IS the iterator
def __next__(self):
if self.start < 0:
raise StopIteration
val = self.start
self.start -= 1
return val
for n in Countdown(3):
print(n) # 3, 2, 1, 0Preview of the next lesson. This section briefly shows the comprehension syntax — a more compact way to write for loops that build lists. The full explanation is in the next lesson (Comprehensions). Read this section as a preview; come back after the Comprehensions lesson and it will make more sense.
Comprehension forms
Python offers four comprehension forms — concise syntax for creating collections from iterables.
# List comprehension
squares = [x**2 for x in range(10)]
# Set comprehension
unique_lengths = {len(word) for word in ["hello", "hi", "hey"]}
# Dict comprehension
word_lengths = {word: len(word) for word in ["hello", "world"]}
# Generator expression (lazy — no [] or {})
sum_squares = sum(x**2 for x in range(1000)) # O(1) memory
# Conditions
evens_squared = [x**2 for x in range(20) if x % 2 == 0]
# Nested (be careful — hard to read beyond 2 levels)
flat = [item for row in [[1,2],[3,4],[5,6]] for item in row]
# [1, 2, 3, 4, 5, 6]
# Walrus operator := (Python 3.8+)
# Assign and use in one expression
numbers = [1, 2, 3, 4, 5, 6, 7, 8]
filtered = [y for x in numbers if (y := x**2) > 10]
# [16, 25, 36, 49, 64]What this section is: Python's match/case statement — a more powerful alternative to long if/elif chains when you're matching against the structure of data. This is an Intermediate concept. In the Beginner tab, just know it exists. Read it properly in the Intermediate tab when you're ready.
Structural pattern matching (Python 3.10+)
The match statement (PEP 634) is much more powerful than a switch/case. It matches on the structure of an object — checking types, destructuring tuples and lists, matching class attributes, and binding matched parts to names.
# Match on value
def http_error(status):
match status:
case 400: return "Bad request"
case 404: return "Not found"
case 418: return "I'm a teapot"
case 500 | 502 | 503: return "Server error"
case _: return f"Unknown: {status}"
# Destructure sequences
def process_point(point):
match point:
case (0, 0): return "origin"
case (x, 0): return f"x-axis at {x}"
case (0, y): return f"y-axis at {y}"
case (x, y): return f"({x}, {y})"
# Match class instances
class Point:
def __init__(self, x, y):
self.x = x; self.y = y
def classify(p):
match p:
case Point(x=0, y=0): return "origin"
case Point(x=x, y=0): return f"x-axis: {x}"
case Point(x=0, y=y): return f"y-axis: {y}"
case Point(x=x, y=y) if x == y: return f"diagonal: {x}"
case Point(): return "some point"while True: loop with a break at the end: while True: body; if condition: break. This is idiomatic Python.for/else does not mean "if the loop found nothing." The else clause runs when the loop completes without hitting a break. If the iterable was empty, the else still runs. It's best read as "no break occurred."[x for x in range(5)] does not create a variable x in the enclosing scope. This was a Python 2 inconsistency that was fixed.✅ Intermediate tab complete — check your understanding
- I can explain what __iter__ and __next__ do and how they power the for loop
- I can write a match/case block for at least one real use case
- I know Python evaluates "and" and "or" lazily (short-circuit evaluation)
Structural pattern matching internals and PEP 634
What you'll learn: The formal semantics of pattern matching (PEP 634), how Python compiles match statements to bytecode, and the full grammar of patterns (OR patterns, AS patterns, mapping patterns, class patterns).
How to read this tab: Read after completing Stage 2. This is for when you're using match/case in production and need to understand exactly what each pattern type can do.
Short-circuit evaluation. In Python, and and or return the actual value that determined the result, not True/False. "Priya" or "default" returns "Priya". None or "fallback" returns "fallback". This pattern is used constantly in professional Python code.
How Python evaluates boolean expressions
Python's boolean operators and, or, not use short-circuit evaluation and return values, not necessarily booleans. x and y: if x is falsy, return x; otherwise return y. x or y: if x is truthy, return x; otherwise return y. This is specified in Python Language Reference §6.12: "These operators are evaluated left to right. Short-circuit evaluation is performed." This enables idiomatic patterns like name = name or "Anonymous" and value = obj and obj.method().
What this section is: The official design document that introduced match/case into Python. Expert-level reading — covers the formal grammar, the rationale for design choices, and what was rejected. Only relevant if you're implementing complex pattern matching or extending the language.
PEP 634 — Structural Pattern Matching
Python 3.10 added structural pattern matching (PEP 634, accepted 2021). Unlike a switch statement (value comparison), pattern matching destructures objects. The match statement evaluates the subject once, then tries each case pattern in order. Patterns can be: literal (value equality), capture (binds a name), wildcard (_), class (checks type + attributes), sequence (matches list/tuple structure), mapping (matches dict keys), OR pattern (|), AS pattern (bind after match), guard (if condition). The implementation uses structural subtyping, not isinstance checks — the __match_args__ class attribute controls positional pattern matching.
✅ Expert tab complete
- I know the difference between a capture pattern, a literal pattern, and a class pattern
- I understand why match/case is not the same as a switch statement