Control Flow

Master conditionals, loops, and iteration patterns — if/elif/else, for, while, range, enumerate, zip, and match/case.

Beginner 30 min read 🐍 Python

Conditionals: Making Decisions

Programs need to make decisions. Should we show an error message? Is the user logged in? Is this number positive? Python uses if, elif (short for "else if"), and else to handle these decisions. Unlike languages that use curly braces, Python uses indentation to define which code belongs to which condition.

if / elif / else

The basic pattern is: check a condition, and if it's true, run the indented code block. You can chain multiple conditions with elif, and provide a fallback with else:

age = 20

if age < 13:
    print("Child")
elif age < 18:
    print("Teenager")
elif age < 65:
    print("Adult")
else:
    print("Senior")
Output
Adult

Python evaluates conditions top to bottom and runs the first block that matches. Once a match is found, it skips all remaining elif and else blocks. This means order matters — put your most specific conditions first.

Indentation is Syntax

Python uses 4 spaces of indentation to define code blocks. This isn't just a style choice — it's part of the language. Mixing tabs and spaces will cause errors. Configure your editor to insert 4 spaces when you press Tab.

Truthy and Falsy Values

In Python, every value has a boolean interpretation. You don't always need explicit comparisons like if x == True or if len(items) > 0. Python considers these values falsy (they evaluate to False):

Everything else is truthy. This lets you write clean, Pythonic conditions:

# Instead of: if len(name) > 0:
name = ""
if name:
    print(f"Hello, {name}")
else:
    print("Name is empty")

# Instead of: if len(items) > 0:
items = [1, 2, 3]
if items:
    print(f"Got {len(items)} items")
Output
Name is empty
Got 3 items
Key Takeaway: Use truthy/falsy checks instead of explicit length or None comparisons. if items: is more Pythonic than if len(items) > 0:.

Ternary Expression (Conditional Expression)

For simple if/else assignments, Python has a one-liner syntax. It reads almost like English: "value if condition else other_value":

age = 20
status = "adult" if age >= 18 else "minor"
print(status)

# Useful in f-strings too
count = 1
print(f"Found {count} item{'s' if count != 1 else ''}")
Output
adult
Found 1 item

For Loops: Iterating Over Data

The for loop is Python's primary iteration tool. Unlike C-style for loops that use counters (for(i=0; i), Python's for loop iterates directly over items in a sequence. This is cleaner, less error-prone, and more readable:

Iterating Over Sequences

# Iterate over a list
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
    print(fruit)

print("---")

# Iterate over a string (character by character)
for char in "Python":
    print(char, end=" ")
Output
apple
banana
cherry
---
P y t h o n 

enumerate() — When You Need the Index

Sometimes you need both the item AND its position. Don't use range(len(list)) — use enumerate() instead. It's cleaner and avoids off-by-one errors:

fruits = ["apple", "banana", "cherry"]

# Bad: C-style indexing
# for i in range(len(fruits)):
#     print(i, fruits[i])

# Good: Pythonic
for i, fruit in enumerate(fruits):
    print(f"{i}: {fruit}")

# Start counting from 1
for i, fruit in enumerate(fruits, start=1):
    print(f"#{i} {fruit}")
Output
0: apple
1: banana
2: cherry
#1 apple
#2 banana
#3 cherry

range() — Generating Number Sequences

range() generates a sequence of numbers. It's lazy — it doesn't create a list in memory, it generates numbers on demand. This makes it efficient even for huge ranges:

# range(stop) - 0 to stop-1
for i in range(5):
    print(i, end=" ")  # 0 1 2 3 4
print()

# range(start, stop) - start to stop-1
for i in range(2, 6):
    print(i, end=" ")  # 2 3 4 5
print()

# range(start, stop, step) - with step size
for i in range(0, 20, 5):
    print(i, end=" ")  # 0 5 10 15
print()

# Counting backwards
for i in range(5, 0, -1):
    print(i, end=" ")  # 5 4 3 2 1
Output
0 1 2 3 4
2 3 4 5
0 5 10 15
5 4 3 2 1

While Loops

A while loop repeats as long as a condition is true. Use it when you don't know in advance how many iterations you need — like reading user input until they type "quit", or processing data until a queue is empty:

# Countdown
count = 5
while count > 0:
    print(count, end=" ")
    count -= 1
print("Go!")
Output
5 4 3 2 1 Go!

break and continue

break exits the loop entirely. continue skips the rest of the current iteration and moves to the next one. Think of break as "I found what I need, stop looking" and continue as "skip this one, try the next":

# break - exit the loop early
for i in range(10):
    if i == 5:
        print(f"Found 5! Stopping.")
        break
    print(i, end=" ")
print()

# continue - skip certain iterations
for i in range(10):
    if i % 3 == 0:
        continue  # Skip multiples of 3
    print(i, end=" ")
Output
0 1 2 3 4 Found 5! Stopping.
1 2 4 5 7 8
🔍 Deep Dive: The else Clause on Loops

Python loops can have an else clause that runs only if the loop completes without hitting break. This is useful for search patterns: if you find what you're looking for, break; otherwise the else block handles the "not found" case. Example: for item in items: if match(item): break followed by else: print("not found"). It's a niche feature but very elegant when you need it.

Loop Patterns

Python has several elegant patterns for common iteration tasks. Learning these will make your code shorter, clearer, and more Pythonic.

Looping Over Dictionaries

Dictionaries support three iteration styles. By default, iterating gives you keys. Use .items() for key-value pairs, .values() for just values:

scores = {"Alice": 95, "Bob": 87, "Charlie": 92}

# Key-value pairs (most common)
for name, score in scores.items():
    print(f"{name}: {score}")

print("---")

# Just keys (default)
for name in scores:
    print(name)

# Just values
total = sum(scores.values())
print(f"Average: {total / len(scores):.1f}")
Output
Alice: 95
Bob: 87
Charlie: 92
---
Alice
Bob
Charlie
Average: 91.3

zip() — Parallel Iteration

Need to iterate over two (or more) sequences at the same time? zip() pairs them up element by element. It stops at the shortest sequence:

names = ["Alice", "Bob", "Charlie"]
scores = [95, 87, 92]
grades = ["A", "B+", "A-"]

for name, score, grade in zip(names, scores, grades):
    print(f"{name}: {score} ({grade})")
Output
Alice: 95 (A)
Bob: 87 (B+)
Charlie: 92 (A-)
Key Takeaway: Use enumerate() for index+value, zip() for parallel iteration, and .items() for dictionaries. Avoid C-style range(len(...)) indexing.

Match/Case (Python 3.10+)

Python 3.10 introduced structural pattern matching — similar to switch/case in other languages, but much more powerful. It can match values, types, and even destructure data:

command = "quit"

match command:
    case "start":
        print("Starting...")
    case "stop" | "quit" | "exit":
        print("Stopping...")
    case str() as cmd if cmd.startswith("set "):
        _, value = cmd.split(" ", 1)
        print(f"Setting: {value}")
    case _:
        print(f"Unknown command: {command}")
Output
Stopping...

The _ wildcard matches anything — it's the default case. The | operator matches any of several values. You can also add if guards for additional conditions.

⚠️ Common Mistake: Modifying a List While Iterating

Wrong:

items = [1, 2, 3, 4, 5]
for item in items:
    if item % 2 == 0:
        items.remove(item)  # Bug! Skips elements
print(items)  # [1, 3, 5]? No! [1, 3, 5] sometimes, [1, 3, 4] other times

Why: Removing items shifts indices, causing the loop to skip elements. The behavior is unpredictable.

Instead: Use a list comprehension to create a new filtered list:

# Correct: create a new list
items = [1, 2, 3, 4, 5]
items = [x for x in items if x % 2 != 0]
print(items)  # [1, 3, 5] — always correct

Practice Exercises

FizzBuzz

Print numbers 1-20. For multiples of 3, print "Fizz". For 5, print "Buzz". For both, print "FizzBuzz".

Sum of Digits

Write a while loop that sums the digits of a number. E.g., 1234 → 1+2+3+4 = 10.

Find the Max

Use a for loop to find the largest number in a list without using the built-in max().