Control Flow

Introduction

While it sounds technical, "control flow" is the idea of repeating, skipping, or directing your code given certain conditions — we call them loops and conditionals! This section will introduce you to:

  • if/else statements

  • for and while loops

  • keywords including break, continue, and enumerate

  • list comprehension syntax

Our goal is to help you master these fundamental tenets of programming, whose applications and concepts reach far beyond Python.


If/Else Statements & Boolean Logic

The idea of if/else statements is grounded in basic logic and can be applied across many programming languages. Python’s syntax for if/else statements is very simple:

racks = False

if racks:
  print("ARE YOU WITH THAT?")
else:
  print("Nope")
Nope

Strict if/else statements imply two options, but we can use if/else-if/else to include 3 or more conditions (for more than 3, just add more else-if statements). The syntax is as follows:

num = 3
if num == 1:
    print("Our number is 1.")
elif num == 3:
    print("Our number is 3.")
else:
    print("Our number is not 1 or 3.")
Our number is 3.


The syntactic differences in Python compared to most languages are the lack of semicolons, curly braces, and parentheses.

Parentheses are still acceptable! If you’re more comfortable putting parentheses around your statements, feel free, but they’re not necessary.

The tradeoff in Python is that indentation is crucial. Statements must be properly indented after the if/else colons, else the program won’t run as desired. Take the following examples:

value = 35
if value < 42:
  print("This value suffices.")
  if value < 32:
    print("This value suffices the lower limit.")
    print("This value suffices both limits.")
This value suffices.


value = 35
if value < 42:
  print("This value suffices.")
  if value < 32:
    print("This value suffices the lower limit.")
  print("This value suffices both limits.")
This value suffices.
This value suffices both limits.

The difference between the two examples is a mere two spaces, but because indentation is so important, the second example does not function as desired. We have a page on indentation if you need more explanation.

Many Python IDEs will allow you to alter the standard amount of space taken up by tabs. We use 2-space tabs for the examples to preserve space, but it’s common to see 4-space tabs, as other programming languages use \t and four spaces interchangeably. Use whatever you please, you just have to stay consistent!


One final note is on the walrus operator. Yes, you read that right. Wanna guess what it looks like?

:=

Nice. Unfortunately, the walrus operator is not an ultra-powerful culmination of everything control flow has to offer, but it is the only structure that uses assignment and expression simultaneously:

if value := 44 > 44:
  print("Value is greater than 44.")
else:
  print("Value is not greater than 44.")
Value is not greater than 44.

While the above example is not exactly intuitive or useful, the walrus operator is crucial for eliminating repetition in longer (and often harder-to-debug) statements. If you want to read more about :=, including its unique parentheses rules, visit the official Walrus Operator page.


Loops: For and While

The tandem-duo of loops and lists is a combination you’ll see a lot in programming:

For Loops

# this list is a pangram, which is not a programming structure,
# but an English one. Programming nerds are not a monolith!
# Some of us like books too
my_list = ["the", "quick", "brown", "fox", "jumped", "over", "the", "lazy", "dog"]
for item in my_list:
    print(item)
the
quick
brown
fox
jumped
over
the
lazy
dog


The underlying structure of a for loop is important:

  • for and in are keywords; they assert "for every element in this list, do some action," which is exactly what Python does.

  • item is a variable that you call within the loop, and it will change depending on the situation: looping through a list of Strings? item uses String functions. Looping through a list of integers? item can use mathematical functions. Call the variable whatever you like; as long as your reference to it is consistent, the name does not matter.

  • my_list (or whatever you have at the end of the statement) is an iterable, or something you can iterate/loop through — this includes lists, sets, and dictionaries. Iterables will return True when tested using iter(element) if you’re unsure of a variable’s compatibility with loops.

Python does not have a "for-each" loop like some object-oriented languages; however, the standard for loop operates much more like a for-each loop than an object-oriented for loop.

Object-Oriented For-Loop ~ Python for x in enumerate(my_iterable) Object-Oriented For-each Loop ~ Python for x in my_iterable

We discuss enumerate a few paragraphs from here.

You will probably run across iterables containing iterables — lists of tuples, tuples of tuples, lists of lists, etc. Python’s for loops can cover these cases as well, as seen in this example using formatting strings.

tuple_of_tuples = (("first", 1), ("second", 2), ("third", 3))

for my_string, my_value in tuple_of_tuples:
    print(f'my_string: {my_string}, my_value: {my_value}')
my_string: first, my_value: 1
my_string: second, my_value: 2
my_string: third, my_value: 3


While Loops

The basis of while loops is iterating through some code while a statement is true or false. As such, something needs to happen within the loop to change the truth of the conditional, otherwise the code will run infinitely. For example:

condition = True
while condition:
    print("I am a fairly useless while loop.")
    condition = False
I am a fairly useless while loop.

For-loops will generally run for the length of the iterable. In a situation where you want a program to run until an exception occurs, but you don’t know when an exception will occur or a condition will change, while loops are a better option.


Nesting

Nesting is a very common aspect of loops and conditionals where statements are included at lower levels to create increasingly specific loops and if/else statements. Leap years, for example, generally happen every four years. However, they do not occur at the turn of the century unless that year is also divisible by 400 (1900 was not a leap year, while 2000 was). We can demonstrate this logic by using a loop: 1

year = 1968

if year % 4 == 0:
    if year % 100 == 0:
        if year % 400 == 0:
            print("{year} is a leap year.")
        else:
            print("{year} is not a leap year.")
    else:
        print("{year} is a leap year.")
else:
    print("{year} is not a leap year.")
1968 is a leap year.


enumerate

Unfortunately, the need for indices while using loops still arises occasionally, and the for-each structure of Python’s for loops fails to account for this. Luckily, enumerate can help:

my_list = ["the", "quick", "brown", "fox", "jumped", "over", "the", "lazy", "dog"]
for idx, val in enumerate(my_list):
    print(f'The index of {val} is {idx}.')
The index of the is 0.
The index of quick is 1.
The index of brown is 2.
The index of fox is 3.
The index of jumped is 4.
The index of over is 5.
The index of the is 6.
The index of lazy is 7.
The index of dog is 8.

One parameter of enumerate is start =, with the default being zero. You can change this to suit your needs:

my_list = ["the", "quick", "brown", "fox", "jumped", "over", "the", "lazy", "dog"]
for idx, val in enumerate(my_list, start = 1):
    print(f'{val} is word #{idx} in the sentence.')
the is word #1 in the sentence.
quick is word #2 in the sentence.
brown is word #3 in the sentence.
fox is word #4 in the sentence.
jumped is word #5 in the sentence.
over is word #6 in the sentence.
the is word #7 in the sentence.
lazy is word #8 in the sentence.
dog is word #9 in the sentence.


break

This keyword will break us out of whatever loop we’re currently in — sometimes, variable names are intuitive.

Two things of note here:

  1. break does not do anything with if/else statements. Your code will not run if you try and break out of an if/else statement.

  2. If break is nested, it only discontinues the current loop, not all of the parent loops.

While we can’t demonstrate code that doesn’t run to verify #1, the following example verifies #2.

letters = ['a', 'b', 'c', 'd', 'e']
nums = [1, 2, 3, 4, 5]
for letter in letters:
    print(letter)
    for num in nums:
        print(num)
        break
a
1
b
1
c
1
d
1
e
1

break is useful for enhancing the functionality of loops, as we can break out of a loop if we reach a certain condition.


continue

This is the keyword counterpart to break; if you want to account for a conditional but continue the loop with the next iteration, you use continue instead of break.

my_list = (1,2,'a',3,4,'b',5)
count = 0
for i in my_list:
    if type(i) == str:
        continue
    count += 1

print(count)
5

continue and break are both very useful in catching exceptions when dealing with an inconsistent iterable.


List Comprehension

Our last section on control flow focuses on a specific, loop-list integration called list comprehension. Like the walrus operator (except more common), list comprehension aims to condense code. We’ll show two equivalent bits of code, each with the following output:

[1, 4, 9, 16, 25]
my_list = [1,2,3,4,5]
my_squares = [i**2 for i in my_list]
print(my_squares)
my_list = [1,2,3,4,5]
my_squares = []
for i in my_list:
    my_squares.append(i**2)
print(my_squares)

Additionally, you can add simple if-statements to your list comprehension statements:

my_list = [1,2,3,4,5]
my_odds = [v for v in my_list if v % 2 == 1]
print(my_odds)
[1, 3, 5]


A similar process can be done for tuples, though it’s generally more complicated and returns objects called generators instead of a new tuple. You can read about it here if you’d like, but for the most part, you won’t be using tuple comprehension.

Dictionaries, on the other hand, do work with list comprehension:

my_dict = {"first": 1, "second": 2, "third": 3}
my_squares = {key:value**2 for key, value in my_dict.items()}
print(my_squares)
{'first': 1, 'second': 4, 'third': 9}


You can use list comprehension on complex nested lists, though the code will look similarly complicated. Once again, we give the output for two equivalent blocks of code:

[1, 3, 5, 1, 3, 1, 3, 5, 7, 1, 3, 5, 7, 9]
my_list = [[1,2,3,4,5], [1,2,3], [1,2,3,4,5,6,7,8], [1,2,3,4,5,6,7,8,9]]
my_odds = [number for a_list in my_list for number in a_list if number % 2 == 1]
print(my_odds)
my_list = [[1,2,3,4,5], [1,2,3], [1,2,3,4,5,6,7,8], [1,2,3,4,5,6,7,8,9]]
my_odds = []
for li in my_list:
    for number in li:
        if number % 2 == 1:
            my_odds.append(number)

print(my_odds)

While list comprehensions can be helpful, nested loops are easier to understand. Due to that it’s often better to use nested loops when writing code.

Shorter code != better code. Code that is clearer without sacrificing runtime is always better than a shorter, uglier, more opaque counterpart.