Chapter 11

for loops and range — running a known number of times

When you know how many times, a for loop is the right choice. The three forms of range, why the end is excluded, and how to choose between for and while.

35 minPython 3.12
  1. 1Encounter
  2. 2Understand
  3. 3Worked
  4. 4Predict
  5. 5Apply
  6. 6Stretch

The problem we are solving

In the last mission, summing 1 to 5 took four lines, two of which existed only to keep count — a starting value and an increment. Forget the increment and you have an infinite loop.

But notice something about that kind of work: we knew in advance how many times it would run. Five. When that is the case there is no reason to shoulder the bookkeeping yourself — you can hand it to Python.

That is exactly what a for loop does. And because the counting is out of your hands, a for loop can never be infinite — which is its single biggest advantage.

By the end of this mission you can

  • Run code a known number of times with a for loop
  • Use all three forms of range()
  • Explain why range(1, 5) produces four numbers, not five
  • Loop over the characters of a piece of text
  • Decide which of for and while a given job wants

Prerequisites: while loops — running while a condition holds.


The simplest form

python
for number in range(5):
    print(number)
text
0
1
2
3
4

Three lines of work in one, with no counter to manage. Read it as: "for each of the numbers `range(5)` produces, run the block once — and each time, put that number in `number`."

Two things stand out immediately:

It starts at zero, and 5 never appears — five numbers in total, but 0 through 4. This is the same rule Python applies everywhere: as the end index is excluded in a slice, so it is in a range.

The name number is your choice. for i in range(5) is equally fine, and i is the conventional name for a counter. But when the number means something, name it for the meaning — for student_number in range(30) reads by itself.

The three forms of range()

python
print("--- range(5) ---")
for i in range(5):
    print(i, end=" ")

print()
print("--- range(2, 6) ---")
for i in range(2, 6):
    print(i, end=" ")

print()
print("--- range(0, 10, 3) ---")
for i in range(0, 10, 3):
    print(i, end=" ")

print()
print("--- range(5, 0, -1) ---")
for i in range(5, 0, -1):
    print(i, end=" ")
print()
text
--- range(5) ---
0 1 2 3 4
--- range(2, 6) ---
2 3 4 5
--- range(0, 10, 3) ---
0 3 6 9
--- range(5, 0, -1) ---
5 4 3 2 1

The rules:

  • range(stop) — starts at 0, ends before stop
  • range(start, stop) — starts at start, ends before stop
  • range(start, stop, step) — advances by step each time; a negative step counts backwards

Check the last two. range(0, 10, 3) gives 0 3 6 9 — the next would be 12, which is past 10, so it stops. And range(5, 0, -1) gives 5 4 3 2 1 — 0 never appears, because the end is excluded here too.

Why is the end excluded? Because it makes the arithmetic easy. How many numbers in range(0, 5)? 5 - 0, so five. In range(3, 8)? 8 - 3, five. Subtract and you have the count, with no wondering whether to add one. And two ranges placed side by side — range(0, 5) and range(5, 10) — miss nothing and repeat nothing.

When you want one to ten

This mistake happens often enough to deserve its own section:

python
print("Wrong — stops at 9:")
for i in range(1, 10):
    print(i, end=" ")

print()
print("Right — includes 10:")
for i in range(1, 11):
    print(i, end=" ")
print()
text
Wrong — stops at 9:
1 2 3 4 5 6 7 8 9
Right — includes 10:
1 2 3 4 5 6 7 8 9 10

The rule in one line: write one more than the last number you want as the stop.

Accumulating — now the easy way

python
total = 0

for number in range(1, 6):
    total = total + number

print("Sum of 1 to 5:", total)
text
Sum of 1 to 5: 15

Compare it with the while version of the same job: no counter to create, none to increment, no risk of an infinite loop. Only the accumulator still has to start outside the loop, exactly as before.

Looping over text

for does not only walk over numbers — it walks over text, character by character:

python
word = "Python"

for letter in word:
    print(letter, end="-")
print()

vowel_count = 0
for letter in "programming":
    if letter in "aeiou":
        vowel_count = vowel_count + 1

print("Vowels:", vowel_count)
text
P-y-t-h-o-n-
Vowels: 3

There is no range here, because none is needed — for letter in word walks the characters directly. It is simpler than doing index arithmetic, and there is no IndexError to risk.

When you need both the position and the character, enumerate() exists, but that belongs to the mission on lists.

break and continue work here too

Exactly as they did in a while:

python
for number in range(1, 20):
    if number % 7 == 0:
        print("First multiple of 7:", number)
        break
text
First multiple of 7: 7

Choosing between for and while

Ask one question: is the number of repetitions known before the loop starts?

If yes, for:

  • Run five times
  • Go from 1 to 100
  • Look at every character of a word
  • Do something with every item in a list

If no, while:

  • Keep asking until the input is valid
  • Run until the person types quit
  • Read while there are lines left in a file

When in doubt, choose for. The reason is simple: a for loop is guaranteed to end, and a while loop is not. Where both would work, the one that allows fewer mistakes is the better choice.


A complete example

multiplication.py:

python
# Print a multiplication table, then a small summary
number = int(input("Which table? "))
upto = int(input("Up to?       "))

print()
print(f"--- Table of {number} ---")

total = 0
for i in range(1, upto + 1):
    result = number * i
    total = total + result
    print(f"{number} x {i:2} = {result:4}")

print()
print(f"Rows    : {upto}")
print(f"Sum     : {total}")
print(f"Average : {total / upto:.2f}")
text
Which table? 7
Up to?       5

--- Table of 7 ---
7 x  1 =    7
7 x  2 =   14
7 x  3 =   21
7 x  4 =   28
7 x  5 =   35

Rows    : 5
Sum     : 105
Average : 21.00

Three things to notice. First, range(1, upto + 1) — reaching upto required writing one more. Second, {i:2} and {result:4} — inside an f-string you can state how much width a number should occupy, which keeps the columns straight. Third, total starts outside the loop and grows inside — the same accumulator as before.

And ask yourself one question: what happens if the person answers 0 to Up to? The range(1, 1) produces no numbers at all, so the loop never runs — and then total / upto divides by zero. It is the same lesson as the previous mission: before using what a loop accumulated, check that it accumulated something.


When it breaks

The last number is missing range(1, 10) finishes at 9. Add one to the stop: range(1, 11).

TypeError: 'float' object cannot be interpreted as an integer range() accepts whole numbers only. range(2.5) or range(1, n / 2) will not run — convert with int(), or divide with //.

SyntaxError: expected ':' The colon at the end of the for line is missing.

IndentationError: expected an indented block after 'for' statement The line inside the loop was not indented.

The loop never ran The range is empty. range(5, 0) produces nothing — to count downwards you need the third argument: range(5, 0, -1).

The total is 0 after the loop The total = 0 line has slipped inside the loop, so it resets every pass. It has to be outside.

A variable from inside the loop is needed outside, but raises NameError The range was empty, so the loop never ran and the variable was never created. Give it a starting value before the loop.