Day 4 / 25 Iteration: Loops 0/0 exercises Exercises ↓

AmouAI Hub/Courses/Programming Fundamentals/Day 4

Week 1 · Python Core Foundations · Day 4

Iteration: Loops

The moment a program can repeat itself, it stops being a calculator and starts being software. Today is the biggest single jump in the whole course.

Study time
4 hours + exercises
Reading
Think Python, Ch. 7
Focus
while, for, accumulators

By the end of today you can

  1. Write while loops with a correct initialise / test / update structure
  2. Use for and range() in all three forms without off-by-one errors
  3. Apply the accumulator pattern to sum, count, track a maximum, and build a string
  4. Use break and continue deliberately rather than by reflex
  5. Reason about nested loops and how many times the body actually runs
  6. Build a trace table to debug any loop, on paper or in your head

Today's videos

Watch each video, then work the matching sections below. Watching alone will not do it.

4A
while and for (120 min)
Why loops · the three parts of a while loop · infinite loops, on purpose · for and range() · the off-by-one factory · the accumulator pattern · break and continue · loop-else · two full programs.
4B
Loop Patterns & Nested Loops (120 min)
Nested loops and 2D thinking · pattern printing · input validation loops · sentinel loops · searching with flags · prime checking · FizzBuzz · a debugging clinic on off-by-one errors.

1Why loops exist

One idea, then the two ways Python spells it.

Print the numbers 1 to 5. Without a loop:

no_loop.py
print(1)
print(2)
print(3)
print(4)
print(5)

Now print 1 to 1,000,000. You see the problem. With a loop, both jobs are the same size:

with_loop.py
for n in range(1, 6):
    print(n)
1 2 3 4 5

The while loop

A while loop keeps going as long as its condition is true. It has three parts, and forgetting any one of them is a bug:

while_basic.py
count = 1              # 1. INITIALISE - set up before the loop

while count <= 5:      # 2. TEST - checked before every pass
    print(count)
    count += 1         # 3. UPDATE - move toward making the test false

print("Done.")
1 2 3 4 5 Done.
Forget the update and the loop never ends

Remove count += 1 and the condition is true forever. Your program hangs. Press Ctrl+C in the terminal to stop it. This will happen to you - it happens to everyone - and recognising it instantly is the skill.

The for loop and range()

Use for when you know how many times, or when you are walking through a sequence.

range.py
range(5)          # 0, 1, 2, 3, 4        - stop only
range(1, 6)       # 1, 2, 3, 4, 5        - start and stop
range(0, 10, 2)   # 0, 2, 4, 6, 8        - start, stop, step
range(5, 0, -1)   # 5, 4, 3, 2, 1        - counting down

for letter in "cat":      # you can loop over a string directly
    print(letter)
c a t
range() excludes the stop value

range(1, 6) gives you 1 through 5. This is deliberate: it means range(n) produces exactly n values, and range(len(s)) produces exactly the valid indices of s. It is also the source of roughly every off-by-one error you will write this week.

Loop trace tableinteractive

Build a loop, then step through it one iteration at a time. The trace table is the tool you should reach for whenever a loop misbehaves.

for i in range( , , ):

2The accumulator pattern

If you learn one shape today, learn this one. It is the answer to a startling proportion of programming problems.

The pattern is always the same four steps:

  1. Initialise an accumulator variable before the loop
  2. Loop over the data
  3. Update the accumulator each time round
  4. Use it after the loop ends
accumulators.py
# SUM
total = 0                    # identity for addition
for n in range(1, 11):
    total += n
print(total)                 # 55

# COUNT
count = 0
for ch in "programming":
    if ch in "aeiou":
        count += 1
print(count)                 # 3

# PRODUCT
product = 1                  # identity for multiplication - NOT 0
for n in range(1, 6):
    product *= n
print(product)               # 120

# MAXIMUM
numbers = [4, 19, 3, 27, 8]
biggest = numbers[0]         # start with a real value, not 0
for n in numbers:
    if n > biggest:
        biggest = n
print(biggest)               # 27

# BUILD A STRING
initials = ""
for word in "ada lovelace byron".split():
    initials += word[0].upper()
print(initials)              # ALB
55 3 120 27 ALB
Initialise correctly or the answer is silently wrong

Start a product at 0 and the answer is always 0. Start a maximum at 0 and it breaks the moment all your data is negative. Start it at the first actual value instead. Neither mistake raises an error.

break and continue

break_continue.py
# break - leave the loop entirely
for n in range(1, 100):
    if n * n > 50:
        print(f"First square over 50 is {n}*{n} = {n*n}")
        break

# continue - skip the rest of THIS pass, go to the next
for n in range(1, 11):
    if n % 2 == 0:
        continue          # skip evens
    print(n, end=" ")
First square over 50 is 8*8 = 64 1 3 5 7 9

The loop else

Unique to Python and genuinely useful: else on a loop runs only if the loop finished without hitting break. It reads as "if we never found it".

loop_else.py
target = 7
for n in [2, 4, 6, 8]:
    if n == target:
        print("Found it")
        break
else:
    print("Not in the list")
Not in the list

3Nested loops

A loop inside a loop. Simple to write, and the first place where the cost of what you wrote starts to matter.

nested.py
for row in range(3):
    for col in range(4):
        print(f"({row},{col})", end=" ")
    print()          # newline after each row
(0,0) (0,1) (0,2) (0,3) (1,0) (1,1) (1,2) (1,3) (2,0) (2,1) (2,2) (2,3)

The inner loop runs completely for every single pass of the outer loop. Three outer passes times four inner passes is twelve executions of the body.

Nested loop gridinteractive

Watch the fill order, and watch the body count. Push both sliders up and notice how fast the number grows - this is your first taste of algorithmic cost.

3 4

Patterns

patterns.py
# right triangle
for row in range(1, 6):
    print("*" * row)

print()

# pyramid - the width of the padding is the thing to work out
size = 5
for row in range(1, size + 1):
    spaces = size - row
    stars = 2 * row - 1
    print(" " * spaces + "*" * stars)
* ** *** **** ***** * *** ***** ******* *********
Ask what each row needs

Do not try to see the whole picture at once. Ask: for row number i, how many spaces and how many stars? Write that formula, then wrap it in a loop. Every pattern problem collapses under that question.

4The patterns you will use constantly

Four loop shapes that cover most of what real programs do with repetition.

1. Input validation - keep asking until it is right

validate.py
while True:
    raw = input("Enter an age (0-120): ")
    if raw.isdigit() and 0 <= int(raw) <= 120:
        age = int(raw)
        break
    print("That is not a valid age. Try again.")

print(f"Thank you. Age recorded as {age}.")
Enter an age (0-120): abc That is not a valid age. Try again. Enter an age (0-120): 200 That is not a valid age. Try again. Enter an age (0-120): 34 Thank you. Age recorded as 34.

while True with a break is the standard idiom here. It looks alarming and is completely normal - the exit condition simply lives in the middle rather than at the top.

2. Sentinel - loop until a terminator value

sentinel.py
total = 0
while True:
    entry = input("Amount (or 0 to finish): ")
    value = float(entry)
    if value == 0:
        break
    total += value

print(f"Total: {total:.2f}")

3. Search with a flag

search.py
numbers = [4, 19, 3, 27, 8]
target = 27

found = False
position = -1
for i in range(len(numbers)):
    if numbers[i] == target:
        found = True
        position = i
        break

if found:
    print(f"Found {target} at position {position}")
else:
    print(f"{target} is not in the list")
Found 27 at position 3

4. A real algorithm - is it prime?

prime.py
n = 97

if n < 2:
    is_prime = False
else:
    is_prime = True
    divisor = 2
    while divisor * divisor <= n:      # only need to check up to the square root
        if n % divisor == 0:
            is_prime = False
            break
        divisor += 1

print(f"{n} prime? {is_prime}")
97 prime? True
Why stop at the square root

If n = a * b and both a and b were larger than √n, their product would exceed n. So at least one factor is always at or below the square root. Checking further is guaranteed to find nothing. For a million-digit check that is the difference between instant and never.

FizzBuzz, and why the order matters

fizzbuzz.py
for n in range(1, 16):
    if n % 15 == 0:          # check the MOST specific case first
        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

Put the n % 3 test first and 15 prints "Fizz" instead of "FizzBuzz". Same trap as yesterday's grade calculator: most specific branch first.

5Debugging loops: the trace table

When a loop is wrong, do not stare at it. Build a table.

This is supposed to sum 1 to 5. It produces 10.

buggy_sum.py
total = 0
for i in range(5):
    total += i
print(total)      # 10, not 15
10

Build the table. One row per iteration, one column per variable, plus what the condition was:

iterationitotal beforetotal after
1000
2101
3213
4336
54610

The table makes it obvious: i never reaches 5, and it wasted an iteration on 0. The fix is range(1, 6). You did not need to be clever - you needed to write down what actually happened.

The five off-by-one signatures

SymptomUsual cause
Result is short by the last itemrange(1, n) where you meant range(1, n+1)
Result includes a spurious 0range(n) where you meant range(1, n+1)
IndexError on the final passrange(len(s) + 1)
Loop runs one time too manywhile i <= n where you meant <
Loop never runs at allCondition already false at the start, or range(5, 1) with no negative step
Print with labels, not bare values

print(total) inside a loop gives you a column of numbers with no context. print(f"i={i} total={total}") gives you the trace table for free. It costs six extra characters and saves twenty minutes.

>_Python playground

A real Python interpreter running inside your browser. Nothing is installed, nothing is uploaded, nothing can break.

scratch.pypython not loaded
Values for input(), comma separated →
Output appears here. The first run takes a few seconds while Python loads.

Exercise set

Checked automatically the moment you submit. Work top to bottom — each one assumes the last. Your answers are saved in this browser.

All Warm-up Core Challenge Reset day

Day 5 — Debugging, Clean Code & Mini-Project 1

How to find bugs on purpose instead of by accident - then build something real.

Continue →