Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Chapter 2: Control Flow

So far, your programs have run from top to bottom, one line after another. Control flow lets a program make decisions, repeat work, and group steps into reusable pieces.

In this chapter you will learn:

  • How to run code only when a condition is true, with if, elif, and else
  • How to write short conditions as one-line expressions and compare a value against several options with match
  • How to repeat code with for and while loops, and how break and continue change a loop’s flow
  • How to define functions with parameters, default values, and return values
  • How variable scope works inside and outside functions

Conditional statements

A conditional statement runs a block of code only if a condition is met. This section covers:

  1. if statements
  2. if-else statements
  3. if-elif-else statements
  4. The match statement
  5. Conditional expressions (the ternary operator)
  6. Combining conditions with and, or, and not

if statements

The simplest conditional statement is the if statement. Its syntax is:

if condition:
    # code to be executed if condition is True

Here, condition is an expression that evaluates to a boolean value (either True or False). If the condition is True, the code inside the if block runs. Otherwise, it is skipped.

The colon at the end of the if line and the indentation below it matter. Python uses indentation, by convention 4 spaces, to tell which lines belong to the block.

Here’s an example:

x = 5

if x > 0:
    print("x is positive")

# Output:
# x is positive

if-else statements

To run one block of code when the condition is True and a different block when it is False, use the if-else statement:

if condition:
    # code to be executed if condition is True
else:
    # code to be executed if condition is False

Here’s an example:

x = -5

if x > 0:
    print("x is positive")
else:
    print("x is not positive")

# Output:
# x is not positive

if-elif-else statements

When you need to check several conditions and run different code depending on which one is met, use the if-elif-else statement (elif is short for “else if”):

if condition1:
    # code to be executed if condition1 is True
elif condition2:
    # code to be executed if condition1 is False and condition2 is True
elif condition3:
    # code to be executed if condition1 and condition2 are False and condition3 is True
...
else:
    # code to be executed if all conditions are False

Python checks the conditions from top to bottom and runs only the first block whose condition is True. You can have as many elif branches as you need, and the final else is optional.

Here’s an example:

x = 0

if x > 0:
    print("x is positive")
elif x < 0:
    print("x is negative")
else:
    print("x is zero")

# Output:
# x is zero

The match statement

When you compare one value against several fixed options, a long if-elif-else chain can get repetitive. Python 3.10 and newer offer the match statement for this:

command = "exit"

match command:
    case "start":
        print("Starting the game")
    case "quit" | "exit":
        print("Goodbye")
    case _:
        print("Unknown command")

# Output:
# Goodbye

Python compares command against each case from top to bottom and runs the first one that matches. The | means “or”, so case "quit" | "exit": matches either word. The underscore _ matches anything, so case _: works like a final else. match can do much more, such as matching the shape of lists and dictionaries, but comparing a value against a few options is its most common use.

Conditional expressions (the ternary operator)

A conditional expression, also known as the ternary operator, picks one of two values based on a condition. It lets you write a simple if-else statement on a single line.

The syntax is:

value_if_true if condition else value_if_false

Here’s the same choice written both ways:

age = 20

# Traditional if-else statement
if age >= 18:
    status = "Adult"
else:
    status = "Minor"
print(status)  # Output: Adult

# Equivalent conditional expression
status = "Adult" if age >= 18 else "Minor"
print(status)  # Output: Adult

In this example, status is set to "Adult" if age is 18 or older, and "Minor" otherwise.

Another example

Suppose you want to describe whether a number is positive or not:

number = 5
result = "Positive" if number > 0 else "Not positive"
print(result)  # Output: Positive

Nested conditional expressions

You can also nest conditional expressions, although this can make code harder to read:

number = 0
result = "Positive" if number > 0 else "Negative" if number < 0 else "Zero"
print(result)  # Output: Zero

In this example, result is "Positive" if number is greater than 0, "Negative" if number is less than 0, and "Zero" if number is exactly 0.

Conditional expressions make code shorter, but use them only for simple choices. For anything more complex, a regular if-elif-else statement is easier to read.

Combining conditions with and, or, and not

You can combine conditions with the logical operators and, or, and not:

age = 25
has_ticket = True

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

if not has_ticket:
    print("Please buy a ticket")

# Output:
# You can enter

The truth table below shows the result of each operator for every combination of A and B:

ABA and BA or Bnot A
FalseFalseFalseFalseTrue
FalseTrueFalseTrueTrue
TrueFalseFalseTrueFalse
TrueTrueTrueTrueFalse

So and is True only when both sides are True, or is True when at least one side is True, and not flips a value.

Loops

Loops let you repeat a block of code multiple times. Python has two kinds: for loops and while loops.

for loops

A for loop goes through a sequence of elements, such as a list, tuple, or string, one element at a time. The syntax for a for loop is:

for element in sequence:
    # code to be executed for each element

Here’s an example of a for loop that goes through a list of numbers and prints each number:

numbers = [1, 2, 3, 4, 5]

for number in numbers:
    print(number)

# Output:
# 1
# 2
# 3
# 4
# 5

On each pass through the loop, the variable number holds the next item from the list.

Counting with range()

To repeat something a set number of times, use the range() function. range(5) produces the numbers 0 to 4: it starts at 0 and stops before 5.

for i in range(5):
    print(i)

# Output:
# 0
# 1
# 2
# 3
# 4

You can also give range() a start value, a stop value, and a step value. This loop starts at 2, stops before 6, and counts up by 2:

for i in range(2, 6, 2):
    print(i)

# Output:
# 2
# 4

Getting the position with enumerate()

If you need each item’s position as well as the item, use enumerate(). It gives you pairs of an index and an item, which you can unpack into two variables:

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

for index, fruit in enumerate(fruits):
    print(index, fruit)

# Output:
# 0 apple
# 1 banana
# 2 cherry

Counting starts at 0. To start at 1, write enumerate(fruits, start=1).

while loops

A while loop repeats a block of code as long as a condition is True. The syntax for a while loop is:

while condition:
    # code to be executed while condition is True

Here’s an example of a while loop that prints the numbers 1 to 5:

i = 1
while i <= 5:
    print(i)
    i += 1

# Output:
# 1
# 2
# 3
# 4
# 5

Make sure something inside the loop eventually makes the condition False. Here, i += 1 increases i each time. Without it, i would stay 1 and the loop would run forever, which is called an infinite loop. If that happens, press Ctrl+C to stop the program.

break and continue

You can use the break and continue statements to control the flow of a loop. break exits the loop completely, while continue skips the rest of the current pass and moves on to the next one. Both work in for and while loops.

i = 1
while True:
    if i > 5:
        break
    elif i % 2 == 0:
        i += 1
        continue
    print(i)
    i += 1

# Output:
# 1
# 3
# 5

while True would run forever on its own, so the break is what ends this loop once i goes past 5. For even numbers, continue jumps back to the top of the loop before print(i) runs, so only the odd numbers are printed.

Functions

A function is a named block of code that does one job. You define it once and then call it whenever you need that job done, instead of repeating the same code.

Defining and calling functions

To define a function, use the def keyword followed by the function name, a set of parentheses containing the function’s parameters, and a colon. The indented lines below form the function’s body:

def greet(name):
    print("Hello, " + name)

greet("John")  # Output: Hello, John
greet("Maria")  # Output: Hello, Maria

Defining a function doesn’t run it. The body runs only when you call the function by writing its name followed by parentheses containing the values you want to pass in, as in greet("John").

Parameters and arguments

These two words are easy to mix up:

  • A parameter is a variable name listed in the function definition. In def greet(name):, name is a parameter.
  • An argument is the actual value you pass when you call the function. In greet("John"), "John" is the argument.

When the function runs, each parameter takes the value of its argument. So during the call greet("John"), name holds "John", and the function prints Hello, John.

Default values and keyword arguments

You can give a parameter a default value, which is used when the caller leaves that argument out. You can also pass arguments by name, called keyword arguments, so their order doesn’t matter:

def greet(name, greeting="Hello"):
    print(f"{greeting}, {name}!")

greet("John")  # Output: Hello, John!
greet("John", "Good morning")  # Output: Good morning, John!
greet(greeting="Hi", name="Sam")  # Output: Hi, Sam!

The string is an f-string, which you met in Chapter 1: Python replaces {greeting} and {name} with their values. Parameters with default values must come after parameters without them in the definition.

Returning a value

Functions can also send a value back to the caller with the return keyword:

def add(x, y):
    """Return the sum of x and y."""
    return x + y

result = add(3, 4)
print(result)  # Output: 7
print(add(10, 5) * 2)  # Output: 30

The add function has two parameters, x and y, and returns their sum. When you call it with the arguments 3 and 4, it returns the value 7, which is stored in the result variable. You can use a returned value anywhere you could use the value itself, such as in a calculation.

The string in triple quotes right below the def line is a docstring. It describes what the function does, and tools such as help(add) display it. Adding one to each function you write is a good habit.

A function without a return statement returns the special value None. Printing inside a function is not the same as returning:

def greet(name):
    print("Hello, " + name)

value = greet("John")  # Output: Hello, John
print(value)  # Output: None

Scope and global variables

The scope of a variable is the part of the program where you can use it. There are two main kinds of scope:

  • A global variable is created outside any function. You can read it anywhere in the file, including inside functions.
  • A local variable is created inside a function, and parameters are local too. It exists only while the function runs.

For example:

x = 10  # global variable

def func1():
    print(x)  # reads the global x

def func2():
    x = 20  # creates a new local variable named x
    print(x)

func1()  # Output: 10
func2()  # Output: 20
print(x)  # Output: 10

The func1 function has no variable named x of its own, so it reads the global x and prints 10.

The func2 function assigns to x, which creates a local variable that shadows the global variable with the same name. Inside func2, x means the local variable, so it prints 20. Outside func2, the global x is unchanged and still has the value 10.

Changing a global variable

Assigning to a variable inside a function always creates a local variable, unless you say otherwise. To change a global variable from inside a function, declare it with the global keyword first:

count = 0

def increment():
    global count
    count += 1

increment()
increment()
print(count)  # Output: 2

Without the global line, count += 1 fails. Python sees the assignment, treats count as a local variable for the whole function, and then finds that this local variable has no value yet when it tries to add 1. The result is an UnboundLocalError. The try and except lines below catch the error and print its message instead of stopping the program. You’ll learn about them in Chapter 4.

count = 0

def increment():
    count += 1  # count is local here, but has no value yet

try:
    increment()
except UnboundLocalError as error:
    print(error)

# Output:
# cannot access local variable 'count' where it is not associated with a value

Python 3.10 words this message differently, but the error is the same.

Using global works, but it is usually better to pass values in as arguments and return the result. A function that only uses its parameters and return value is easier to understand, test, and reuse:

def increment(count):
    return count + 1

count = 0
count = increment(count)
count = increment(count)
print(count)  # Output: 2

Key takeaways

  • if, elif, and else run different code depending on conditions. Python runs only the first block whose condition is True.
  • match (Python 3.10+) compares one value against several options, and case _ catches everything else.
  • for loops go through each item in a sequence. range() produces a series of numbers, and enumerate() gives you each item’s index too.
  • while loops repeat as long as a condition is True, so make sure it eventually becomes False. break leaves a loop early, and continue skips to the next pass.
  • Functions receive arguments through their parameters, can have default values, and send results back with return.
  • Assigning to a variable inside a function makes it local. Prefer returning values over changing global variables with global.

Practice

Exercise 1: Letter grades

Store a test score from 0 to 100 in a variable named score. Print the letter grade: A for 90 and above, B for 80 to 89, C for 70 to 79, D for 60 to 69, and F below 60. Try several scores to check every branch.

Solution
score = 82

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"A score of {score} gets a {grade}")
# Output:
# A score of 82 gets a B

Exercise 2: FizzBuzz

Print the numbers from 1 to 15. For multiples of 3, print Fizz instead of the number. For multiples of 5, print Buzz. For multiples of both 3 and 5, print FizzBuzz.

Solution
for number in range(1, 16):
    if number % 3 == 0 and number % 5 == 0:
        print("FizzBuzz")
    elif number % 3 == 0:
        print("Fizz")
    elif number % 5 == 0:
        print("Buzz")
    else:
        print(number)
# Output:
# 1
# 2
# Fizz
# 4
# Buzz
# Fizz
# 7
# 8
# Fizz
# Buzz
# 11
# Fizz
# 13
# 14
# FizzBuzz

The check for both 3 and 5 has to come first. If it came after the check for 3, Python would print Fizz for 15 and never reach it.

Exercise 3: Counting vowels

Write a function count_vowels(text) with a docstring. It should return the number of vowels (a, e, i, o, u) in text, counting uppercase and lowercase letters. Test it on "Hello, World" and "Python".

Solution
def count_vowels(text):
    """Return the number of vowels in text."""
    count = 0
    for letter in text.lower():
        if letter in "aeiou":
            count += 1
    return count

print(count_vowels("Hello, World"))  # Output: 3
print(count_vowels("Python"))  # Output: 1

text.lower() converts the text to lowercase, so you only need to check lowercase vowels.