Level 1 · First Words

Loops That Count: for 🔂

If while is 'keep going until', for is 'do this once for each of these'. It is the loop you will write ninety percent of the time.

for: once for each item

for item in ["rubber chicken", "grog", "map", "sword"]:
    print(f"You are carrying: {item}")
You are carrying: rubber chicken
You are carrying: grog
You are carrying: map
You are carrying: sword

Read it as English: "for each item in this collection, do the following". The variable item is created by the loop and takes each value in turn. You do not declare it, you do not increment it, and you cannot get the counting wrong, because there is no counting.

VOLITION[Medium: Success]

Notice what just disappeared. No counter to initialise, no condition to get backwards, no increment to forget, no off-by-one at the end. An entire genus of bug, extinct, because you described what you wanted instead of how to step through it.

range: when you want numbers

for n in range(5):
    print(n)
0
1
2
3
4

range(5) gives five numbers starting at 0: 0, 1, 2, 3, 4. Five numbers, not up to five. This is the same start-included, end-excluded rule as slicing, and it is consistent throughout the language.

print(list(range(5)))
print(list(range(1, 6)))
print(list(range(0, 20, 5)))
print(list(range(10, 0, -2)))
[0, 1, 2, 3, 4]
[1, 2, 3, 4, 5]
[0, 5, 10, 15]
[10, 8, 6, 4, 2]
WrittenMeans
range(stop)0 up to but not including stop
range(start, stop)start up to but not including stop
range(start, stop, step)as above, jumping by step (negative counts down)

Looping over text

for letter in "GROG":
    print(letter, end=" ")

print()

word = "banana"
count = 0
for letter in word:
    if letter == "a":
        count += 1
print(f"{count} letter a's in {word}")
G R O G
3 letter a's in banana

enumerate: when you need the position too

crew = ["Guybrush", "Elaine", "Otis"]

# The clumsy way, which you see beginners write:
for i in range(len(crew)):
    print(f"{i}: {crew[i]}")

print("---")

# The Python way:
for i, name in enumerate(crew):
    print(f"{i}: {name}")

print("---")

# Humans count from 1:
for position, name in enumerate(crew, start=1):
    print(f"{position}. {name}")
0: Guybrush
1: Elaine
2: Otis
---
0: Guybrush
1: Elaine
2: Otis
---
1. Guybrush
2. Elaine
3. Otis
🎯 The rule of thumb

If you ever write for i in range(len(something)), stop. You almost certainly want for item in something, or enumerate if you genuinely need the index. Experienced reviewers spot that pattern instantly.

zip: walking two collections together

names = ["Guybrush", "Elaine", "LeChuck"]
roles = ["pirate", "governor", "ghost"]

for name, role in zip(names, roles):
    print(f"{name:10} is a {role}")
Guybrush   is a pirate
Elaine     is a governor
LeChuck    is a ghost

zip stops at the shortest one, which is usually what you want and occasionally a silent bug. If you need it to complain about mismatched lengths, use zip(a, b, strict=True), added in Python 3.10.

Accumulating: the pattern behind everything

prices = [4.50, 12.00, 3.25, 8.75]

total = 0
for price in prices:
    total += price

print(f"Total: {total:.2f}")
print(f"Average: {total / len(prices):.2f}")
print(f"Built in: {sum(prices):.2f}")
Total: 28.50
Average: 7.12
Built in: 28.50

Start with an empty accumulator, add to it each time round, use it after. That shape (with a list, a string, a dictionary or a counter) is behind a huge fraction of all programs. Python also has sum, min, max and len built in, and you should use them when they fit.

Nested loops

for row in range(1, 4):
    for col in range(1, 4):
        print(f"{row * col:3}", end="")
    print()
  1  2  3
  2  4  6
  3  6  9

The inner loop runs completely for every single step of the outer one: three rows times three columns is nine prints. This is how you handle grids, tables, chessboards and images. It is also where performance goes to die: two nested loops over 1,000 items each is a million steps. Lesson 51 has more to say about that.

FizzBuzz, finally complete

for n in range(1, 21):
    if n % 15 == 0:
        print("FizzBuzz")
    elif n % 3 == 0:
        print("Fizz")
    elif n % 5 == 0:
        print("Buzz")
    else:
        print(n)
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz

That is the whole of the famous interview screening question. It exists because a startling number of applicants cannot write it, and what it really tests is whether you thought about the order of the conditions.

Exercise 1

Times table

Print the 7 times table from 7 x 1 to 7 x 12, one line each, neatly aligned.

Reveal solution
for n in range(1, 13):
    print(f"7 x {n:2} = {7 * n:3}")
7 x  1 =   7
7 x  2 =  14
7 x  3 =  21
7 x  4 =  28
7 x  5 =  35
7 x  6 =  42
7 x  7 =  49
7 x  8 =  56
7 x  9 =  63
7 x 10 =  70
7 x 11 =  77
7 x 12 =  84
Exercise 2

Count the vowels

Count how many vowels are in a phrase, and report which ones appeared.

Reveal solution
phrase = "The Secret of Monkey Island"

vowels = "aeiou"
count = 0
found = ""

for letter in phrase.lower():
    if letter in vowels:
        count += 1
        if letter not in found:
            found += letter

print(f"{count} vowels")
print(f"which were: {found}")
8 vowels
which were: eoia

in works on strings as well as lists, and reads exactly as you would say it out loud. It is one of Python's nicest small features.

Exercise 3

Draw a triangle

Print a right-angled triangle of stars, five rows tall, then the same triangle upside down.

Reveal solution
for row in range(1, 6):
    print("*" * row)

print()

for row in range(5, 0, -1):
    print("*" * row)
*
**
***
****
*****

*****
****
***
**
*

No inner loop needed: multiplying a string does the repetition for you. When you can replace a loop with an expression, the code usually gets clearer, not just shorter.

+100 XP