In the previous tutorial, we learned about variables, types, and f-strings. Now let’s learn how to make decisions and repeat actions in Python.

Control flow statements let your program choose what to do based on conditions and repeat actions in loops. By the end of this tutorial, you will know how to use if, for, while, match/case, and several useful loop helpers.

if, elif, else

The if statement runs code only when a condition is true:

temperature = 28

if temperature > 35:
    print("It is very hot!")
elif temperature > 25:
    print("It is warm.")
elif temperature > 15:
    print("It is mild.")
elif temperature > 5:
    print("It is cold.")
else:
    print("It is freezing!")

Output: It is warm.

Python checks each condition from top to bottom. It runs the first block where the condition is True and skips the rest.

Notice the colon : at the end of each line and the indentation (4 spaces) for the code block. Python uses indentation instead of curly braces.

Comparison Operators

x = 10
print(x == 10)   # True — equal
print(x != 5)    # True — not equal
print(x > 5)     # True — greater than
print(x < 20)    # True — less than
print(x >= 10)   # True — greater than or equal
print(x <= 10)   # True — less than or equal

Logical Operators

Combine conditions with and, or, and not:

age = 25
has_ticket = True

if age >= 18 and has_ticket:
    print("You can enter.")

if age < 13 or age > 65:
    print("You get a discount.")

if not has_ticket:
    print("Buy a ticket first.")

Ternary Expression

For simple conditions, use a one-liner:

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

This is the same as:

if age >= 18:
    status = "adult"
else:
    status = "minor"

Use ternary expressions for simple cases. For complex conditions, use regular if/else.

for Loops

The for loop iterates over a sequence:

fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
    print(fruit)

Output:

apple
banana
cherry

range()

Use range() to loop a specific number of times:

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

# range(2, 8) gives 2, 3, 4, 5, 6, 7
for i in range(2, 8):
    print(i)

# range(0, 10, 2) gives 0, 2, 4, 6, 8 (step of 2)
for i in range(0, 10, 2):
    print(i)

A common mistake: range(5) goes from 0 to 4, not 0 to 5. The end value is not included.

enumerate()

When you need both the index and the value:

fruits = ["apple", "banana", "cherry"]
for index, fruit in enumerate(fruits):
    print(f"{index}: {fruit}")

Output:

0: apple
1: banana
2: cherry

Without enumerate(), you would need a counter variable. enumerate() is cleaner and more Pythonic.

zip()

When you need to loop over two lists at the same time:

names = ["Alex", "Sam", "Jordan"]
scores = [95, 87, 92]

for name, score in zip(names, scores):
    print(f"{name}: {score}")

Output:

Alex: 95
Sam: 87
Jordan: 92

zip() stops at the shorter list. If one list has 3 items and the other has 5, you get 3 pairs.

while Loops

A while loop runs as long as its condition is true:

count = 5
while count > 0:
    print(count)
    count -= 1
print("Go!")

Output:

5
4
3
2
1
Go!

Be careful with while loops. If the condition never becomes False, you get an infinite loop. Always make sure something in the loop changes the condition.

break and continue

break — Stop the Loop

break exits the loop immediately:

numbers = [1, 2, 0, 3, 4]
for num in numbers:
    if num == 0:
        break  # Stop when we hit zero
    print(num)

Output:

1
2

The loop stops at 0 and does not print 3 or 4.

continue — Skip to the Next Iteration

continue skips the rest of the current iteration and moves to the next one:

numbers = [1, -2, 3, -4, 5]
for num in numbers:
    if num < 0:
        continue  # Skip negative numbers
    print(num)

Output:

1
3
5

for…else

Python has an unusual feature: you can put else after a for loop. The else block runs only if the loop completed without hitting a break:

# Check if a number is prime
def is_prime(n: int) -> bool:
    for i in range(2, n):
        if n % i == 0:
            return False  # Found a divisor, not prime
    else:
        # Loop completed without finding a divisor
        return True

print(is_prime(13))  # True
print(is_prime(15))  # False

This is useful when you search for something in a loop. If you find it (and break), the else does not run. If you do not find it, the else runs.

Most Python developers rarely use for...else. But it shows up in interviews, so it is good to know.

The Walrus Operator :=

The walrus operator (:=) assigns a value and uses it in the same expression. It was added in Python 3.8:

# Without walrus operator
line = input("Enter something: ")
while line != "quit":
    print(f"You said: {line}")
    line = input("Enter something: ")

# With walrus operator — cleaner
while (line := input("Enter something: ")) != "quit":
    print(f"You said: {line}")

It is also useful in list comprehensions:

words = ["hi", "python", "go", "programming"]
# Filter words >= 5 chars and convert to uppercase
long_words = [
    upper
    for word in words
    if (upper := word.upper()) and len(word) >= 5
]
print(long_words)  # ['PYTHON', 'PROGRAMMING']

The walrus operator avoids calling the same function twice. Use it when you need to both assign and test a value.

match/case (Python 3.10+)

Python’s match/case is similar to switch in other languages, but more powerful. It can match values, sequences, and even object structures.

Matching Values

def describe_http_status(code: int) -> str:
    match code:
        case 200:
            return "OK"
        case 301:
            return "Moved Permanently"
        case 404:
            return "Not Found"
        case 500:
            return "Internal Server Error"
        case _:
            return "Unknown"

print(describe_http_status(404))  # Not Found

The _ is a wildcard. It matches anything, like default in a switch statement.

Guards

Add if conditions to case statements:

match code:
    case _ if 200 <= code < 300:
        return "Success"
    case _ if 400 <= code < 500:
        return "Client Error"
    case _ if 500 <= code < 600:
        return "Server Error"
    case _:
        return "Unknown"

Matching Sequences

match/case can destructure tuples and lists:

point = (3, 0)
match point:
    case (0, 0):
        print("origin")
    case (x, 0):
        print(f"on x-axis at x={x}")
    case (0, y):
        print(f"on y-axis at y={y}")
    case (x, y):
        print(f"point at ({x}, {y})")

Output: on x-axis at x=3

Matching Command Strings

command = "move north 10"
parts = command.split()

match parts:
    case ["quit"]:
        print("quitting")
    case ["hello", name]:
        print(f"greeting {name}")
    case ["move", direction, distance]:
        print(f"moving {direction} by {distance}")
    case _:
        print("unknown command")

Output: moving north by 10

match/case is much more powerful than a chain of if/elif statements for these patterns. Use it when you have complex branching logic.

Looping Over Dictionaries

You can loop over dictionaries in several ways:

user = {"name": "Alex", "age": 25, "city": "Berlin"}

# Loop over keys
for key in user:
    print(key)  # name, age, city

# Loop over values
for value in user.values():
    print(value)  # Alex, 25, Berlin

# Loop over key-value pairs
for key, value in user.items():
    print(f"{key}: {value}")

The .items() method is the most useful. It gives you both the key and value in each iteration.

Nested Loops

You can put loops inside loops:

for i in range(3):
    for j in range(3):
        print(f"({i}, {j})", end=" ")
    print()  # New line after each row

Output:

(0, 0) (0, 1) (0, 2)
(1, 0) (1, 1) (1, 2)
(2, 0) (2, 1) (2, 2)

Be careful with nested loops. They can be slow if the lists are large.

Practical Example: FizzBuzz

Let’s combine everything into a classic programming exercise:

for i in range(1, 21):
    match (i % 3, i % 5):
        case (0, 0):
            print("FizzBuzz")
        case (0, _):
            print("Fizz")
        case (_, 0):
            print("Buzz")
        case _:
            print(i)

This uses match/case with tuple patterns. For numbers divisible by both 3 and 5, it prints “FizzBuzz.” For 3 only, “Fizz.” For 5 only, “Buzz.” Otherwise, the number itself.

Practical Example: Simple Menu

Here is a common pattern for command-line programs:

while True:
    print("\n--- Menu ---")
    print("1. Say hello")
    print("2. Count to 10")
    print("3. Quit")

    choice = input("Choose: ")

    match choice:
        case "1":
            name = input("Your name: ")
            print(f"Hello, {name}!")
        case "2":
            for i in range(1, 11):
                print(i, end=" ")
            print()
        case "3":
            print("Goodbye!")
            break
        case _:
            print("Invalid choice. Try again.")

This pattern shows while True with break, match/case, and for loops working together.

Comprehensions Preview

We will cover list comprehensions in detail in Tutorial #6. But here is a quick preview, since they are related to loops:

# Regular for loop
squares = []
for x in range(5):
    squares.append(x ** 2)

# Same thing with a list comprehension
squares = [x ** 2 for x in range(5)]

List comprehensions are shorter and more Pythonic. They are one of Python’s most loved features.

Common Mistakes

Off-by-One with range()

# WRONG: this prints 0 to 4, not 0 to 5
for i in range(5):
    print(i)  # 0, 1, 2, 3, 4

# RIGHT: use range(6) to include 5
for i in range(6):
    print(i)  # 0, 1, 2, 3, 4, 5

Forgetting the Colon

# WRONG
if x > 5
    print("big")

# RIGHT
if x > 5:
    print("big")

Infinite while Loops

# WRONG: count never changes
count = 5
while count > 0:
    print(count)
    # Forgot: count -= 1  <-- infinite loop!

# RIGHT
count = 5
while count > 0:
    print(count)
    count -= 1

Summary

Here is a quick reference for everything we covered:

StatementPurposeExample
if/elif/elseMake decisionsif x > 0: ...
forIterate over a sequencefor item in items:
whileLoop while condition is truewhile count > 0:
breakExit the loopif found: break
continueSkip to next iterationif skip: continue
match/casePattern matchingmatch value: case 1: ...
range()Generate numbersrange(10)
enumerate()Index + valuefor i, v in enumerate(lst):
zip()Parallel iterationfor a, b in zip(x, y):
:=Walrus operatorif (n := len(a)) > 10:

These are the tools you use to control the flow of your programs. You will use if, for, and while in almost every Python program you write.

Source Code

You can find the code for this tutorial on GitHub:

kemalcodes/python-tutorial — tutorial-04-control-flow

Run the examples:

python src/py04_control_flow.py

Run the tests:

python -m pytest tests/test_py04.py -v

What’s Next?

In the next tutorial, we will learn about functions: def, parameters, *args, **kwargs, lambda functions, and closures.