3.2. Iteration#

Hide code cell source

import sys
from pathlib import Path

current = Path.cwd()
for parent in [current, *current.parents]:
    if (parent / '_config.yml').exists():
        project_root = parent  # ← Add project root, not chapters
        break
else:
    project_root = Path.cwd().parent.parent

sys.path.insert(0, str(project_root))

from shared import thinkpython, diagram, jupyturtle

# Register as top-level modules so direct imports work in subsequent cells
sys.modules['thinkpython'] = thinkpython
sys.modules['diagram'] = diagram
sys.modules['jupyturtle'] = jupyturtle

When we need to iterate over the items in a collection or sequence, we typically use a for loop, which is specifically designed for this task. A for loop can loop through an object because the object is an iterable. An iterable is any Python object that can return its elements one at a time, allowing you to loop over it. Common Iterables in Python include:

  • Sequences: lists, tuples, strings, ranges

  • Collections: sets, dictionaries, frozensets

  • Files: file objects

  • Custom objects: anything implementing the iterator protocol

The ability to repeat a block of code is one of the most powerful features in programming. We call this iteration. Python provides two primary loop constructs to handle this:

  • for loops: Designed for definite iteration. Use these when you know in advance how many times code needs to run (e.g., iterating over a list of items).

  • while loops: Designed for indefinite iteration. Use these when you want to repeat a task as long as a specific condition is true, even if you don’t know exactly how many times that will be.

3.2.1. Iterators and Iterables#

Python iterators are objects that implement __iter__() and __next__(), which are special “dunder” (double-under) methods that define Python’s iterator protocol. You don’t call these directly; instead, you use the built-in functions iter() and next(), which invoke them internally. Iterables are objects that implement iter() and can return an iterator via the built-in iter() function.

Collections like lists, tuples, dictionaries, and sets are all iterable objects. They are iterable containers from which you can get an iterator using the iter() method:

tuple_fruit = ("apple", "banana", "cherry")
itr = iter(tuple_fruit)     ### create iterator

print(next(itr))            ### iterator itr has a `next()` method
print(next(itr))
print(next(itr))
apple
banana
cherry

3.2.2. For Loops#

The for loops are mostly used when you have a block of code that you want to repeat a fixed number of times and to execute code for each item. With a for loop, you want to either:

  • iterate over the items in a data collection (such as list, tuple, string, and range, or

  • iterate through a sequence of numbers using the range() constructor, or using the index for index-based iteration.

In both situations, the number of iterations is known. There are times when we do not know the number of elements we are iterating through, e.g., when working with files, though.

for line in open('data.txt'):
    print(line)

Commonly used for loop patterns are:

For Loop Type

Typical Syntax

Iterates Over

Best Use Case

Key Idea

Basic for-each

for x in collection:

Elements

Processing items

Loop through values

range() counting

for i in range(n):

Numbers

Counting, repeats

Like a counting loop

Index-based

for i in range(len(lst)):

Index numbers

When index needed

Access by position

enumerate()

for i, x in enumerate(lst):

Index + value

Cleaner indexing

Best practice

Nested for loop

for x in A: for y in B:

Combinations

Grids, tables

Loop inside loop

Note that other for loop patters include looping through objects such as dictionaries and files.

3.2.2.1. Loop through Iterables#

Python’s basic for loop works much like the foreach loop in other programming languages. Here’s the basic syntax:

for item in iterable:
    <block>

In this syntax, we have:

  • the for keyword

  • the loop variable, item, which holds the current value in the target data collection

  • the body of a block of code that will run one iteration as the loop pulls one item from the iterable.

Once the loop finishes iterating over the data, the execution flow jumps to the next statement after the loop.

Here are two quick examples of a for loop in Python. In the first example, we use a for loop to traverse a list of fruit names. The loop body contains a call to print(), which displays each fruit in each loop.

3.2.2.1.1. Loop through lists#

fruits = ['apple', 'banana', 'cherry']  ### iterable object

for fruit in fruits:
    print(fruit)
apple
banana
cherry
nums = [ 1, 2, 3, 4, 5 ]

for num in nums:
    print(num**2, end=" ")
1 4 9 16 25 

3.2.2.1.2. Loop through strings#

Note that this is character by character.

for letter in 'hello, world':
    print(letter, end=' ')
h e l l o ,   w o r l d 
### Exercise: Basic For Loop
# Write a for loop that iterates through the list `prices` 
# and prints each price with a 10% tax added (price * 1.1).

prices = [10.00, 20.00, 100.00]

# TODO: Write your loop here




### your code ends here

Hide code cell source

### Solution
prices = [10.00, 20.00, 100.00]

for price in prices:
    print(price * 1.1)
11.0
22.0
110.00000000000001

3.2.2.2. Looping with range#

You use plain range(n) when you just need to repeat something *n* times.

for i in range(5):
    print(i, end = " ")
0 1 2 3 4 

3.2.2.3. Index-Based for Loop#

You use range(len(x)) specifically when you need the index to access x[i].

numbers = [10, 20, 30, 40]

for i in range(len(numbers)):
    print(i, numbers[i])       ### numbers[i] is the value at that position
0 10
1 20
2 30
3 40

3.2.2.4. Nested Loops#

A nested loop is a loop inside the body of another loop. The “inner” loop finishes all of its iterations for every single iteration of the “outer” loop.

Syntax and Structure:

for outer_item in outer_sequence:
    ### outer loop body
    for inner_item in inner_sequence:
        # inner loop body executes for each outer_item

A classic use case is generating a multiplication table. For every number i (rows), we iterate through numbers j (columns).

for i in range(2, 9):          # Outer loop (rows)
    for j in range(2, 9):      # Inner loop (columns)
        print(f"{i*j}", end="\t")
    print()                    # Newline after each row
4	6	8	10	12	14	16	
6	9	12	15	18	21	24	
8	12	16	20	24	28	32	
10	15	20	25	30	35	40	
12	18	24	30	36	42	48	
14	21	28	35	42	49	56	
16	24	32	40	48	56	64	

Nested loops are essential for working with multi-dimensional data, like matrices (lists of lists).

matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]

for row in matrix:
    for item in row:
        print(item, end=" ")
    print()  # Newline after printing a full row
1 2 3 
4 5 6 
7 8 9 
### Exercise: Star Pattern
# Use a nested loop to print the following pattern of stars:
# *
# * *
# * * *
# * * * *
# * * * * *

# Hint: The outer loop controls the rows (range 1 to 6).
# The inner loop controls how many stars print on that row.
for row in range(1, 6):
    # TODO: write the inner loop
    
    print() # Newline after each row

Hide code cell source

### Solution
for row in range(1, 6):
    for star in range(row):
        print("*", end=" ")
    print()
* 
* * 
* * * 
* * * * 
* * * * * 

3.2.2.5. Advanced for Loop#

Python provides several advanced techniques that make your code more elegant and readable:

  • enumerate() - getting index and value

  • zip() - parallel iteration

  • Looping through dictionaries

3.2.2.5.1. Enumerate#

Use enumerate() when you need both the position and the value in a loop.

### enumerate example
fruits = ['apple', 'banana', 'cherry', 'date']

for index, fruit in enumerate(fruits):
    print(f"{index}: {fruit}")
0: apple
1: banana
2: cherry
3: date
colors = ["red", "blue", "green"]

for index, color in enumerate(colors):
    print(index, color)
0 red
1 blue
2 green

3.2.2.5.2. zip#

### zip example
names = ['Alice', 'Bob', 'Charlie']
ages = [25, 30, 35]
cities = ['New York', 'London', 'Tokyo']

for name, age, city in zip(names, ages, cities):
    print(f"{name} \t {age} \t {city}")
Alice 	 25 	 New York
Bob 	 30 	 London
Charlie 	 35 	 Tokyo

3.2.2.5.3. Loop through Dictionaries#

person = {
    'name': 'Alice',
    'age': 25,
    'city': 'New York',
    'job': 'Engineer'
}

# Unpacking key-value pairs
for key, value in person.items():
    print(f"{key}: {value}")
print()
# Unpacking keys only
for key in person.keys():
    print(f"Key: {key}")
print()
# Unpacking values only
for value in person.values():
    print(f"Value: {value}")
name: Alice
age: 25
city: New York
job: Engineer

Key: name
Key: age
Key: city
Key: job

Value: Alice
Value: 25
Value: New York
Value: Engineer

3.2.2.6. Accumulator Pattern#

Many loops exist to build up a single result from a collection: a total, a count, a product, or a new list. This is called the accumulator pattern, and it always has three steps:

  1. Initialize the accumulator before the loop, to the value the result should have if the collection were empty: 0 for a sum or a count, 1 for a product, [] for a list.

  2. Update the accumulator once per iteration inside the loop body, usually with an augmented assignment such as +=.

  3. Use the result after the loop ends. Only then does the accumulator hold the final answer.

The example below adds up the prices in a shopping cart.

prices = [3.50, 1.25, 4.00, 2.25]

total = 0                       ### 1. initialize before the loop
for price in prices:
    total += price              ### 2. update once per item
print(f"Total: ${total:.2f}")   ### 3. use the result after the loop
Total: $11.00

A trace table shows how the accumulator changes on each iteration:

Iteration

price

total after total += price

before the loop

(none)

0

1

3.50

3.50

2

1.25

4.75

3

4.00

8.75

4

2.25

11.00

Two mistakes are common. If you put total = 0 inside the loop, the accumulator is reset on every iteration and the result is only the last price. If you put the print() inside the loop, you see a running total after each item instead of one final answer.

The same three steps work for other kinds of results. Only the starting value and the update change:

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

count = 0          ### counting: start at 0, add 1
product = 1        ### multiplying: start at 1, not 0
evens = []         ### collecting: start with an empty list

for num in nums:
    if num % 2 == 0:
        count += 1
        evens.append(num)
    product *= num

print("Even count:", count)    # 2
print("Product:", product)     # 120
print("Evens:", evens)         # [2, 4]
Even count: 2
Product: 120
Evens: [2, 4]
### Exercise: Accumulator
# Initialize a variable `total_rainfall` to 0.
# Iterate through the list `weekly_rain`.
# Add each value to `total_rainfall` using the += operator.
# Print the total at the end.

weekly_rain = [1.2, 0.5, 2.3, 0.0, 1.1]
total_rainfall = 0

### TODO: your code starts here





print(f"Total rainfall: {total_rainfall}")

### TODO: your code stops here
Total rainfall: 0

Hide code cell source

### Solution
weekly_rain = [1.2, 0.5, 2.3, 0.0, 1.1]
total_rainfall = 0

for rain in weekly_rain:
    total_rainfall += rain

print(f"Total rainfall: {total_rainfall}")
Total rainfall: 5.1

3.2.3. while Loops#

A Python while loop repeatedly executes a block of code as long as a specified condition remains True. Once the condition evaluates to False, the program continues with the statement immediately following the loop.

Common patterns for using while loops include:

Type

Controlled By

Loop Continues While…

Ends When

Typical Use Case

Counting (condition-controlled)

A numeric counter

Counter satisfies a condition (e.g., count < 10)

The counter’s condition becomes False

You know roughly how many repetitions you want, expressed as a number

Sentinel (data-controlled)

A special “stop” value in the incoming data

The data value ≠ the sentinel

The sentinel value shows up in the data

Reading input/data of unknown length, until a specific stop signal appears (e.g., typing "q" to quit)

while True (infinite, break-controlled)

Nothing — the condition is always True

Forever, by default

A break statement inside the loop body fires

Menus, games, event loops — situations where the exit condition is checked inside the loop, possibly in multiple places

The core distinction of the three types of while loops are: Counting — you’re tracking how many times you’ve looped. Sentinel — you’re watching what value just came in. while True — you’re not checking anything in the while line itself; the real exit logic lives inside the loop body via break.

The general syntax of the while loop is:

while [condition]:
    [statement(s)]     ### body; runs repeatedly when condition is True
times = 0
while times < 3:
    print("Hello!")
    times += 1
Hello!
Hello!
Hello!

The execution process of a while loop follows a specific cycle:

  1. Evaluate: Python checks if the condition is True.

  2. Execute: If True, the indented body runs.

  3. Repeat: After the body finishes, execution jumps back to step 1.

  4. Exit: If the condition evaluates to False, the loop stops, and the program proceeds to the code below it.

3.2.3.1. Counting while (Condition-Controlled)#

In a counting while loop:

  • A counter with initial value.

  • Loop repeats while a numeric condition remains true.

  • A while loop requires the counter as a variable to increment/decrement to be ready. In this example, we need to define an indexing variable, count.

  • “Off-by-one errors”: Python indexing is 0-based, so use < instead of <= for aligned boundary.

count = 0                        ### initialization of the variable
while count < 10:                ### condition to check
    print(count, end = " ") 
    count = count+1              ### update counter
0 1 2 3 4 5 6 7 8 9 

3.2.3.2. Sentinel while Loop (Data-Controlled)#

In a sentinel while loop, the loop continues until a special value (the “sentinel”) is entered.

In the example below, fake_inputs simulates what a user types, using iter() and next() from the iterators section, so the code runs in the book. To try it with real keyboard input, enable Live Code, uncomment the input() lines, and comment out the next(fake_inputs) lines below them.

### fake_inputs simulates what a user types; uncomment the input() lines for actual use
fake_inputs = iter([5, 12, -1])

# number = int(input("Enter a number (-1 to quit): "))
number = next(fake_inputs)

while number != -1:
    print("You entered:", number)
    # number = int(input("Enter a number (-1 to quit): "))
    number = next(fake_inputs)

print("Program ended.")
You entered: 5
You entered: 12
Program ended.
### Practice: Sentinel Loop
# Write a loop that keeps running until `user_input` is equal to "q".
# For testing in the book, get each value with `user_input = next(fake_inputs)`
# inside the loop instead of using input().
# Print each value as "You entered: <value>".
fake_inputs = iter(["apple", "banana", "q"])
### Your code starts here.



### Your code ends here.

Hide code cell source

### Solution
fake_inputs = iter(["apple", "banana", "q"])

user_input = ""
while user_input != "q":
    user_input = next(fake_inputs)
    print(f"You entered: {user_input}")
You entered: apple
You entered: banana
You entered: q

3.2.3.3. while True loop (Infinite Loop with break)#

A while True loop is infinite by default — it doesn’t stop on its own, so you create a condition inside it to exit manually via break.

while True:
    print("This runs forever")

In the code below, fake_inputs again simulates what a user types. To try it with real keyboard input, enable Live Code, uncomment the input() line, and comment out the next(fake_inputs) line below it.

### fake_inputs simulates what a user types; uncomment the input() line for actual use
fake_inputs = iter(["hello", "world", "q"])

while True:
    # text = input("Enter something (q to quit): ")
    text = next(fake_inputs)

    if text == "q":                 ### when condition met
        break                       ### break!!

    print("You entered:", text)

print("Program ended.")
You entered: hello
You entered: world
Program ended.

Another example with number.

### fake_inputs simulates what a user types; uncomment the input() line for actual use
fake_inputs = iter([10, 20, 5, -1])

total = 0

while True:
    # number = int(input("Enter a number (-1 to stop): "))
    number = next(fake_inputs)
    print("You entered:", number)
    if number == -1:
        break

    total += number

print("Total:", total)
You entered: 10
You entered: 20
You entered: 5
You entered: -1
Total: 35

A while True loop in Python emulates the do while loop in languages like C, Java, or JavaScript, where the code block is run at least once before the condition is checked to decide whether to terminate. The syntax of the do while loop is:

do:
    run code
while condition is true

In Java:

do {
    System.out.println("Hello");
} while (x < 5);

3.2.4. Loop Control Statements#

You can control the execution of a for or while loop using the following statements:

  1. break – Immediately exits the loop, even if the loop condition is still True.

  2. continue – Skips the remainder of the current iteration and returns to the top of the loop to recheck the condition.

  3. pass - Used as a placeholder to write empty loops (also used for empty control statements, functions, and classes).

  4. else clause – Executes only if the loop finishes normally (when the condition becomes False), and does not run if the loop is terminated by a break statement.

### 1/4. break statement for
for i in range(10):
    if i == 5:
        break
    print(i, end = " ")       ### output: 0 1 2 3 4
0 1 2 3 4 
### 1/4. break statement while
i = 0
while i < 10:
    print(i, end = " ")
    if i == 5:
        break
    i += 1
0 1 2 3 4 5 
### 2/4. continue statement for 
for i in range(10):
    if i == 5:
        continue
    print(i, end = " ")
0 1 2 3 4 6 7 8 9 

continue + while is one of the most common infinite-loop bugs, that is, if you place your increment after the print() so continue would skip the increment forever.

### 2/4. continue statement while 
i = 0
while i < 10:
  i += 1
  if i == 5:
    continue
  print(i, end = " ")
1 2 3 4 6 7 8 9 10 
### 3/4. pass statement for for loop

for i in range(10):
    pass  # TODO: implement later
print(i)
9
### 3/4. pass statement for while loop

i = 0
while i < 10:
    i += 1
    pass         ### TODO: do something later
    print("after pass")     ### this will run, after pass!!!
  
print('Value of i :', i)
after pass
after pass
after pass
after pass
after pass
after pass
after pass
after pass
after pass
after pass
Value of i : 10
### 4/4. else statement with loops
for i in range(10):
    print(i, end=' ')
else:
    print("Loop is over") 
0 1 2 3 4 5 6 7 8 9 Loop is over
### 4/4. else statement with loops
i = 0
while i < 10:
    print(i, end=' ')
    i += 1
else:
    print("Loop is over")
0 1 2 3 4 5 6 7 8 9 Loop is over

pass works similarly to continue in a while loop, with distinctions:

Feature

pass

continue

Definition

A null operation; it literally does nothing.

Skips the remainder of the current loop iteration.

Context

Can be used anywhere (loops, functions, classes, if statements).

Only allowed inside loops (for or while).

Impact on Flow

Execution continues to the next statement in the block.

Execution jumps back to the start of the loop for the next cycle.

Primary Use

Syntactic placeholder (prevents “empty block” errors).

Logic control (skips specific items based on a condition).

Example Scenario

Creating an empty function for future implementation.

Printing only odd numbers by skipping even ones.

### Exercise: Break and Continue
# Loop through numbers from 1 to 20.
# 1. If the number is divisible by 4, skip it (use continue).
# 2. If the number is 18, stop the loop entirely (use break).
# 3. Otherwise, print the number.

for i in range(1, 21):
    # TODO: check for 18 first (break)
    
    # TODO: check for divisible by 4 (continue)
    
    print(i, end=" ")
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 

Hide code cell source

### Solution
for i in range(1, 21):
    if i == 18:
        break
    
    if i % 4 == 0:
        continue
    
    print(i, end=" ")
1 2 3 5 6 7 9 10 11 13 14 15 17