Skip to main content
Python · lesson 7 of 34
Python beginner Lesson 7 of 34

Control Flow in Python

Run if/elif/else, match-case, for and while loops, for/else, break/continue and comprehensions in Python, each example shown with its real output.

By SkillByExample · Updated

Output below is from Python 3.13.

if / elif / else

Conditional statements let your program make decisions based on runtime values. Python uses indentation (4 spaces by convention) to delimit blocks instead of braces, and there are no parentheses required around the condition.

# grade.py
score = 85

if score >= 90:
    grade = "A"
elif score >= 80:
    grade = "B"      # this branch runs because 85 >= 80
elif score >= 70:
    grade = "C"
else:
    grade = "F"

print(grade)
$ python grade.py
B

Inline Conditional (Ternary)

For simple two-branch conditions, Python’s ternary expression keeps the logic on one readable line. Reserve it for simple cases — nesting ternaries hurts readability fast.

# ternary.py
age = 20
status = "adult" if age >= 18 else "minor"
print(status)

age = 12
status = "adult" if age >= 18 else "minor"
print(status)
$ python ternary.py
adult
minor

Truthiness Shortcuts

Python evaluates any object in a boolean context, not just explicit True/False. Leveraging this makes conditions more concise and idiomatic — the second form below is considered un-Pythonic.

# truthiness.py
items = []

if not items:
    print("Empty (first)")

if len(items) == 0:
    print("Empty (second)")
$ python truthiness.py
Empty (first)
Empty (second)

match-case (Python 3.10+)

match-case is structural pattern matching — a significant upgrade over a traditional value-switch. It can destructure sequences, match object attributes, and bind variables in a single step, which eliminates the nested if/elif chains that were previously needed for command parsing or protocol handling.

# match_case.py
def handle_command(command):
    match command.split():
        case ["quit"]:
            return "Exiting."
        case ["go", direction]:
            # 'direction' is bound to the second word automatically
            return f"Going {direction}"
        case ["pick", "up", item]:
            return f"Picked up {item}"
        case ["drop", *items]:
            # *items captures all remaining words into a list
            return f"Dropped: {', '.join(items)}"
        case _:
            # _ is the wildcard — matches anything not caught above
            return f"Unknown command: {command}"

print(handle_command("go north"))
print(handle_command("pick up sword"))
print(handle_command("drop key map"))
$ python match_case.py
Going north
Picked up sword
Dropped: key, map

Matching on Types and Attributes

match-case can match against class types and bind specific attribute values in the same case clause — something that would require multiple isinstance calls and attribute accesses with traditional if/elif.

# match_dataclass.py
from dataclasses import dataclass

@dataclass
class Point:
    x: float
    y: float

def classify(point):
    match point:
        case Point(x=0, y=0):
            return "Origin"
        case Point(x=0, y=y):
            return f"Y-axis at {y}"
        case Point(x=x, y=0):
            return f"X-axis at {x}"
        case Point(x=x, y=y):
            return f"Point at ({x}, {y})"

print(classify(Point(0, 0)))
print(classify(Point(0, 5)))
print(classify(Point(3, 4)))
$ python match_dataclass.py
Origin
Y-axis at 5
Point at (3, 4)

The x: float annotation is not enforced — Point(0, 5) stores the int 5, and that is what the pattern binds and prints. Class patterns compare with ==, so Point(0.0, 0.0) would match Point(x=0, y=0) too.

for Loops

Python’s for loop iterates over any iterable — lists, strings, ranges, generators, files, dictionaries, and more. There is no C-style for (i=0; i<n; i++) in Python; if you need an index, use enumerate().

# loops.py
fruits = ["apple", "banana", "cherry"]

for fruit in fruits:
    print(fruit)

print("---")

for i in range(5):        # 0, 1, 2, 3, 4
    print(i)

print("---")

for i in range(2, 10, 2): # 2, 4, 6, 8  (start, stop, step)
    print(i)
$ python loops.py
apple
banana
cherry
---
0
1
2
3
4
---
2
4
6
8

enumerate() and zip()

enumerate() and zip() solve two of the most common looping patterns — getting an index alongside the value, and iterating two sequences together — without manually managing index variables.

# enumerate_zip.py
fruits = ["apple", "banana", "cherry"]

for i, fruit in enumerate(fruits, start=1):
    print(f"{i}. {fruit}")

print("---")

names = ["Alice", "Bob"]
scores = [95, 87]

for name, score in zip(names, scores):
    print(f"{name}: {score}")
$ python enumerate_zip.py
1. apple
2. banana
3. cherry
---
Alice: 95
Bob: 87

for / else

The else clause on a for loop runs when the loop completes normally — that is, without hitting a break. This is useful for “search and report not found” patterns without needing a sentinel variable.

# for_else.py
def find_prime(numbers):
    for n in numbers:
        for i in range(2, int(n**0.5) + 1):
            if n % i == 0:
                break       # composite — inner loop exits early
        else:
            # else runs only when the inner loop finished without break
            return n        # found a prime
    return None

print(find_prime([4, 6, 8, 9, 10, 13, 15]))
$ python for_else.py
13

while Loops

while loops repeat as long as their condition is truthy. They’re the right tool when you don’t know in advance how many iterations you need — waiting for user input, reading until EOF, or polling until a condition is met.

# while_loops.py
count = 0
while count < 5:
    print(count)
    count += 1
$ python while_loops.py
0
1
2
3
4

Walrus Operator in while

The walrus operator (:=) is particularly useful in while loops — it reads a value, assigns it, and checks it in a single expression, eliminating the boilerplate of reading before the loop and again at the top of each iteration.

# walrus.py
import io

data = io.StringIO("line1\nline2\nline3\n")
while line := data.readline():
    print(f"Got: {line.strip()}")
$ python walrus.py
Got: line1
Got: line2
Got: line3

break and continue

break and continue give you fine-grained control over loop execution. They’re most useful when a condition inside the loop body is easier to express than rewriting the loop condition itself.

# break_continue.py
for n in range(10):
    if n == 5:
        break
    print(n)

print("---")

for n in range(10):
    if n % 2 == 0:
        continue       # skip even numbers
    print(n)           # 1, 3, 5, 7, 9
$ python break_continue.py
0
1
2
3
4
---
1
3
5
7
9

Comprehensions

Comprehensions provide a concise syntax for constructing new collections from existing iterables. They’re not just syntactic sugar — Python compiles them to optimized bytecode that’s typically faster than equivalent for loops with .append() calls.

List Comprehension

# list_comprehensions.py
squares = [x**2 for x in range(10)]
print(squares)

evens = [x**2 for x in range(1, 11) if x % 2 == 0]
print(evens)

matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [cell for row in matrix for cell in row]
print(flat)
$ python list_comprehensions.py
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
[4, 16, 36, 64, 100]
[1, 2, 3, 4, 5, 6, 7, 8, 9]

Dict Comprehension

# dict_comprehensions.py
words = ["hello", "world", "python"]
word_lengths = {word: len(word) for word in words}
print(word_lengths)

original = {"a": 1, "b": 2, "c": 3}
inverted = {v: k for k, v in original.items()}
print(inverted)
$ python dict_comprehensions.py
{'hello': 5, 'world': 5, 'python': 6}
{1: 'a', 2: 'b', 3: 'c'}

Set Comprehension

# set_comprehension.py
numbers = [1, 2, 2, 3, 3, 3, 4]
unique_squares = {x**2 for x in numbers}
print(unique_squares)
$ python set_comprehension.py
{16, 1, 4, 9}

The duplicates collapsed to four squares, but notice the printed order: a set has no order of its own, so it lists elements in hash-table slot order, which is why 16 can come out first.

Generator Expression

A generator expression looks like a list comprehension but produces values lazily — one at a time, on demand. Use it when you only need to iterate once or when the full collection would use too much memory.

# generators.py
list_total = sum([x**2 for x in range(1_000_000)])
gen_total = sum(x**2 for x in range(1_000_000))
print(list_total == gen_total)
$ python generators.py
True

Both produce the same sum — the list comprehension builds a million-element list in memory first, while the generator expression streams values one at a time. For sum(), the generator is more memory-efficient and just as fast.

Common Patterns

Flat map

# flat_map.py
sentences = ["hello world", "foo bar baz"]
words = [word for sentence in sentences for word in sentence.split()]
print(words)
$ python flat_map.py
['hello', 'world', 'foo', 'bar', 'baz']

Conditional expression in comprehension

# abs_values.py
values = [1, -2, 3, -4, 5]
abs_values = [x if x >= 0 else -x for x in values]
print(abs_values)
$ python abs_values.py
[1, 2, 3, 4, 5]

Early exit with next()

next() with a generator expression efficiently finds the first match in a sequence without scanning the whole thing. The second argument is the default if nothing matches.

# early_exit.py
users = [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]

alice = next((u for u in users if u["name"] == "Alice"), None)
print(alice)

nobody = next((u for u in users if u["name"] == "Nobody"), None)
print(nobody)
$ python early_exit.py
{'id': 1, 'name': 'Alice'}
None

Practice

1. Prime number finder: Write a function that finds all prime numbers up to a given limit using a for...else loop.

Solution
def primes_up_to(limit):
    primes = []
    for n in range(2, limit + 1):
        for i in range(2, int(n**0.5) + 1):
            if n % i == 0:
                break
        else:
            primes.append(n)
    return primes

print(primes_up_to(20))
$ python primes.py
[2, 3, 5, 7, 11, 13, 17, 19]

The else clause runs when the inner loop completes without break — meaning no divisor was found, so n is prime.

2. Match-case command parser: Extend the handle_command function to also handle a "help" command that returns a list of available commands.

Solution
def handle_command(command):
    match command.split():
        case ["quit"]:
            return "Exiting."
        case ["help"]:
            return "Commands: quit, go, pick, drop"
        case ["go", direction]:
            return f"Going {direction}"
        case ["pick", "up", item]:
            return f"Picked up {item}"
        case ["drop", *items]:
            return f"Dropped: {', '.join(items)}"
        case _:
            return f"Unknown command: {command}"

print(handle_command("help"))
$ python help_command.py
Commands: quit, go, pick, drop

3. Comprehension with filter: Use a list comprehension with a filter to find all words longer than 4 characters in a sentence.

Solution
sentence = "The quick brown fox jumps over the lazy dog"
long_words = [word for word in sentence.split() if len(word) > 4]
print(long_words)
$ python long_words.py
['quick', 'brown', 'jumps']

The comprehension filters the split result, keeping only words where len(word) > 4. With > 5 the result would be [] — no word in this sentence has six letters.

Frequently Asked Questions

Does Python have a switch statement?
Python 3.10 introduced match-case, which is more powerful than a traditional switch — it supports structural pattern matching, not just value comparison.
What's the difference between break and continue?
break exits the loop entirely. continue skips the rest of the current iteration and jumps to the next one.
When should I use a list comprehension vs a for loop?
Use a comprehension when you're building a new list from an iterable with a simple transformation or filter. Use a for loop when the body has side effects or multiple steps.
What does for/else do in Python?
The else clause on a for loop runs when the loop completes normally — without hitting a break. It's useful for search-and-report-not-found patterns without a sentinel variable.
Are generator expressions always better than list comprehensions?
Not always. Generator expressions are more memory-efficient since they don't create the full list. But you can only iterate them once. If you need to iterate multiple times or index into the result, use a list comprehension.