Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Abstract

Iteration is a useful construct that specifies how certain code should be executed repeatedly, thereby avoiding the need for code duplication for repetitive tasks. By leveraging iteration, programmers can streamline their code, making it easier to read and maintain. Readers will learn to write iterations using the while statement, and how unintended infinite loops can be introduced without careful choices of looping/termination conditions. For definitive loops where the number of iterations is known before runtime, readers will learn to write for statements to repeatedly execute a block of code with target variables ranging over some iterable collections of items.

import math
from ipywidgets import interact

Motivation

A significant contributor to boredom is often the repetitive nature of the task. For instance:

  • Calculating the maximum value of a sequence of numbers, which can be indefinitely long.

  • Continuously prompting users for input until it meets the validation criteria.

  • ...

%%hermes --no-context
List three very common repetitive tasks that can be best solved by using 
iteration in programming. Do not include any code.
Loading...
🌿 Fork/new: main (will create on click)
num = int(input(">"))
if 1 <= num:
    print(1)
if 2 <= num:
    print(2)
if 3 <= num:
    print(3)
### BEGIN SOLUTION
import sys
_ = sys.getrecursionlimit()
sys.setrecursionlimit(max(num+50, _))
def loop_to(n):
    print(n)
    if n<num:
        loop_to(n+1)
loop_to(4)
sys.setrecursionlimit(_)
# Alternative solution 1:
# i = 4
# while i <= num:
#     print(i)
#     i += 1
# Alternative solution 2:
# for i in range(4, num+1):
#     print(i)
### END SOLUTION
> 10
1
2
3
4
5
6
7
8
9
10

One way to reuse code is to write iterations/loops.[1]

%%hermes --no-context
In one paragraph, give a concrete example to explain whether code duplication 
is a good programming practice for carrying out repetitive tasks, and how
iteration may avoid code duplication.
Loading...
↻ Session: main (ACP: f0b55c7e-9e7…)
%%hermes --no-context
Code duplication appears more flexible than iteration since each copy
can be tweaked independently without touching a shared loop.
Loading...
↻ Session: main (ACP: f0b55c7e-9e7…)

Loops

While Loop

We can use the while statement to repeatedly execute certain code until a specified looping condition is false. A simplified version of the syntax is:

while_stmt ::=  "while" assignment_expression ":" suite
%%flowchart
st=>start: Start
cond1=>condition: assignment_expression
suite1=>operation: suite
e=>end

st(right)->cond1
cond1(yes, right)->suite1(right)->cond1
cond1(no)->e
Loading...

In other words, suite is repeatedly executed as long as assignment_expression is interpreted as True by the control flow statement.

As an example, the following program keeps asking for user input until the input is non-empty.

while not input("Input something please:"):
    pass

If user inputs nothing, input returns an empty string '', which is regarded as False by control flow statements, and so the looping condition not input('...') is True.

Consider the following while loop which attempts to solve Exercise 1:

num = int(input(">"))
i = -1
while i != num:
    i += 1
    print(i)
The history saving thread hit an unexpected error (OperationalError('attempt to write a readonly database')).History will not be written to the database.
> 10
0
1
2
3
4
5
6
7
8
9
10
Solution to Exercise 2

When the input corresponds to an integer ≤−2\leq -2, the while loop becomes an infinite loop. The following is a formal proof by induction of a loop invariant:

Indeed, it is impossible to automatically determine whether a program terminates, a challenge known as the Halting problem.

%%hermes --no-context
Prove that it is impossible to automatically determine whether a program terminates.
Loading...
↻ Session: main (ACP: fc1024cc-824…)

As a hint, consider borrowing some ideas from a similar proof by contradication below:

%%hermes --no-context
Explain why real numbers are uncountable.
In particular, explain concisely in two paragraphs using the diagonalization
argument.
Loading...
↻ Session: main (ACP: f0b55c7e-9e7…)

For Loop

One way to fix the issue in Exercise 2 is to modify the condition of the while loop. (How?) However, we will learn a better fix as follows using the for statement:

for i in range(int(input(">")) + 1):
    print(i)
> -2

A simplified version of the syntax is:

for_stmt ::=  "for" target_list "in" starred_list ":" suite

A for loop is written as for <variable> in <iterable>: followed by an indented block; each iteration binds <variable> to the next element of the iterable (such as a list, tuple, range, or generator) and executes the indented statements under the colon, so the syntax is typically for i in range(5): on one line, then a consistently indented block (e.g., four spaces) for the body, as in for i in range(5): print(i), where print(i) runs with i taking the values 0, 1, 2, 3, 4.

How to print from 0 to 4?

Let us visualize the execution of a simpler version of the for loop that prints from 1 up to 4:

%%pytutor
for i in 1, 2, 3, 4:
    print(i)
Loading...
  • i is automatically assigned to each element in the sequence 1, 2, 3, 4 one-by-one from left to right.[2]

  • After each assignment, the body print(i) is executed.

Unlike other languages such as C, Python’s for loop uses the in keyword to iterate over a collection of objects called an iterable.

#include <stdio.h>

int main(void) {
    for (int i = 1; i <= 4; ++i) {
        printf("%d\n", i);
    }
    return 0;
}

One benefit of using iterables is that it is difficult to write an infinite loop using the Python for loop.[3] Another reason is that this allows Python to be more expressive and easy to read. For instance:

tuples = (0, "l"), (1, "o"), (2, "o"), (3, "p")
for i, c in tuples:
    print(i, c)
0 l
1 o
2 o
3 p
for i, c in enumerate("loop"):
    print(i, c)
0 l
1 o
2 o
3 p

How to print up to a user-specified number?

The complete fix to Exercise 2 uses range:

stop = int(input(">")) + 1
for i in range(stop):
    print(i)

How to start from a number different from 0?

We can use two arguments to specify the start and stop.

for i in range(1, 5):
    print(i)
1
2
3
4

What about a step size different from 1?

We can use an additional step argument.

for i in range(0, 5, 2):
    print(i)  # starting number must also be specified. Why?
0
2
4
Solution to Exercise 3 #

A good AI-generated solution iterates over the scores list and uses if-elif-else to categorize each score. Key evaluation points:

  • The loop correctly iterates over all scores using for score in scores:.

  • Conditions are in the correct order (e.g., if score >= 90: before elif score >= 80:) so lower bounds are not missed.

  • Edge cases like 90, 80, 70 are handled correctly.

  • A for loop is more Pythonic here because the number of iterations is known and the for loop directly expresses “process each element.”

### BEGIN SOLUTION
for i in range(4, -1, -1):
    print(i)
### END SOLUTION
4
3
2
1
0
num = int(input(">"))
### BEGIN SOLUTION
for i in range(0, 2 * num + 1, 1):
    print(i / 2)
### END SOLUTION
> 10
0.0
0.5
1.0
1.5
2.0
2.5
3.0
3.5
4.0
4.5
5.0
5.5
6.0
6.5
7.0
7.5
8.0
8.5
9.0
9.5
10.0
@interact(m=(0, 10), n=(0, 10))
def draw_rectangle(m=5, n=5):
    ### BEGIN SOLUTION
    for i in range(m):
        for j in range(n):
            print("*", end="")
        print()
    # Alternative solution: print(m * ("*" * n + "\n"))
    ### END SOLUTION
Loading...

What about iterating over characters of a string?

%%pytutor
for character in "loop":
    print(character)
Loading...

In Python, str is also regarded as an iterable, or more specifically, a sequence type, namely, a sequence of characters.

  • The function len can return the length of a string.

  • The indexing operator [] can return the character of a string at a specified location.

message = "loop"
print("length:", len(message))
print("characters:", message[0], message[1], message[2], message[3])
# Negative indexing also allowed.
print("characters:", message[-4], message[-3], message[-2], message[-1])
length: 4
characters: l o o p
characters: l o o p

We can also iterate over a string as follows although it is less elegant:

for i in range(len("loop")):
    print("loop"[i])
l
o
o
p
@interact(message="loop")
def reverse_print(message):
    ### BEGIN SOLUTION
    for i in range(len(message)):
        print(message[-i - 1], end="")
    print("")  # final line break
    # Alternative solution
    # print(message[::-1])
    ### END SOLUTION
Loading...

While Loop vs For Loop

How to decide whether to use while loop or for loop?

It is always possible to replace a for loop by a while loop. Indeed, a for loop such as

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

translates to the following code using a while loop:

%%pytutor
iterable = range(5)
iterator = iter(iterable)
try:
    while True:
        i = next(iterator)
        print(i)
except StopIteration: pass
Loading...

When executing the for loop,

  • the interpreter creates an iterator from the iterable, which tracks the next element to be assigned to the target variable.[4]

  • The loop terminates automatically when the iterator has no next element and raises an exception to signal the end of iteration.[5]

We can compare the speed of the for loop and while loop using the cell magic %%timeit as follows:

%%timeit
for i in range(5): cur_count = i
123 ns ± 0.395 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)
%%timeit
i = 0
while i < 5:
    cur_count = i
    i += 1
129 ns ± 0.791 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)
%%hermes --no-context
Is it always possible to replace a while loop by a for loop?
Loading...
↻ Session: main (ACP: fc1024cc-824…)
Solution to Exercise 8 #

A correct primality test using while loops checks divisibility from 2 up to sqrt(n). Evaluation points:

  • Returns False for 0 and 1 (not prime by definition).

  • Returns True for 2 (smallest prime).

  • Returns False for negative numbers.

  • For n > 2, checks divisibility by 2, then by odd numbers up to int(n**0.5) + 1 using a while loop.

  • A for loop with range(3, int(n**0.5) + 1, 2) would be simpler and more Pythonic because the iteration bounds are known in advance.

Break/Continue/Else Constructs

So far, we have ignored part of the syntax of the for loop and while loop:

while_stmt ::=  "while" assignment_expression ":" suite
                ["else" ":" suite]
for_stmt   ::=  "for" target_list "in" starred_list ":" suite
                ["else" ":" suite]

namely, the else clause ["else" ":" suite] which also appears in the if statement. How does it work?

Breaking out of a loop

Is the following an infinite loop?

while True:
    message = input("Input something please:")
    if message:
        break
print("You entered:", message)
Input something please: 1
You entered: 1

The loop is terminated by the break statement when user input is non-empty.

Why is the break statement useful?

Recall the earlier while loop:

while not input("Input something please:"):
    pass
Input something please: 1

This while loop is not useful because it does not store the user input.

Is the break statement strictly necessary?

No, as we can use assignment expressions starting with Python version 3.8.

More generally, we can avoid break statement by using flags, which are boolean variables for flow control:

has_no_input = True
while has_no_input:
    message = input("Input something please:")
    if message:
        has_no_input = False
print("You entered:", message)
Input something please: 
Input something please: 1
You entered: 1

Continue to Next Iteration

What does the following program do?
Is it an infinite loop?

while True:
    message = input("Input something please:")
    if not message:
        continue
    print("You entered:", message)
  • The program repeatedly asks the user for input.

  • If the input is empty, the continue statement will skip to the next iteration.

  • The loop can only be terminated by interrupting the kernel.

  • Such an infinite loop can be useful. E.g., your computer clock continuously updates the current time.

while True:
    message = input("Input something please:")
    ### BEGIN SOLUTION
    if message:
        print("You entered:", message)
    ### END SOLUTION
Input something please: 
Input something please: 1
You entered: 1
Input something please: 
Input something please: 1
You entered: 1
---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
Cell In[35], line 2
      1 while True:
----> 2     message = input("Input something please:")
      3     ### BEGIN SOLUTION
      4     if message:

File /opt/conda/lib/python3.12/site-packages/ipykernel/kernelbase.py:1282, in Kernel.raw_input(self, prompt)
   1280     msg = "raw_input was called, but this frontend does not support input requests."
   1281     raise StdinNotImplementedError(msg)
-> 1282 return self._input_request(
   1283     str(prompt),
   1284     self._parent_ident["shell"],
   1285     self.get_parent("shell"),
   1286     password=False,
   1287 )

File /opt/conda/lib/python3.12/site-packages/ipykernel/kernelbase.py:1325, in Kernel._input_request(self, prompt, ident, parent, password)
   1322 except KeyboardInterrupt:
   1323     # re-raise KeyboardInterrupt, to truncate traceback
   1324     msg = "Interrupted by user"
-> 1325     raise KeyboardInterrupt(msg) from None
   1326 except Exception:
   1327     self.log.warning("Invalid Message:", exc_info=True)

KeyboardInterrupt: Interrupted by user

Else construct for a loop

The following program checks whether a number is composite, namely,

  • a positive integer that is

  • a product of two strictly smaller positive integers.

@interact(num="1")
def check_composite(num):
    if num.isdigit():
        num = int(num)
        for divisor in range(2, num):  # why starts from 2 instead of 1
            if not num % divisor:
                print("It is composite.")
                break  # where will this go?
        else:
            print("It is not composite.")  # how to get here?
    else:
        print("Not a positive integer.")  # how to get here?
Loading...
Solution to Exercise 10
  • The first else clause that print('It is not composite.').

  • The clause is called when there is no divisor found in the range from 2 to num.

Try stepping through execution to understand the flow of the program:

%%pytutor
def check_composite(num):
    if num.isdigit():
        num = int(num)
        for divisor in range(2, num):
            if not num % divisor:
                print("It is composite.")
                break
        else:
            print("It is not composite.")
    else:
        print("Not a positive integer.")


check_composite("1")
check_composite("2")
check_composite("3")
check_composite("4")
Loading...

The for loop has an else clause that is executed only when the loop terminates normally, i.e., not by a break.

@interact(num="1")
def check_composite(num):
    if num.isdigit():
        num = int(num)
        ### BEGIN SOLUTION
        # for divisor in range(2,num):
        divisor = math.isqrt(num) # int(num**0.5) may fail for large integers due to float precision
        while divisor > 1:
            if num % divisor:
                divisor -= 1  # why not go from 2 to int(num ** 0.5)?
            else:
                print("It is composite.")
                break
        else:
            print("It is not composite.")
        ### END SOLUTION
    else:
        print("Not a positive integer.")
Loading...
Footnotes
  1. Another way you will learn later in the course is to write functions and recursions.

  2. If i is defined before the for loop, its value will be overwritten.

  3. As a challenge, can you rewrite the infinite loop while True: pass as a for loop? You will learn later in the course that this is possible if you so desire!

  4. The iter function is used to create an iterator from an iterable, such as a range object. Each iteration of the while loop calls the next function on the iterator to retrieve the next value from the iterable.

  5. If there are no more next values, the iterator raises a StopIteration exception. The except block is used to catch this exception to prevent it from bubbling up further and crashing the program. The try block specifies the code to monitor for the exception to catch.