Python loops — for and while theory with the café till, change coin by coin (and the while that just kept skipping table 3)

The week before my FP1 midterm I sat down to practise in the faculty café. The exercise was trivial: go through tables 1 to 6 and “clean” them, skipping table 3 because it was reserved. I wrote a while, added a continue to skip table 3, hit run… and the terminal started spitting out the same line over and over, my laptop fan took off and the guy at the next table looked at me as if I had broken something. This is what my program did (with an emergency brake I added so I can show you the output without it hanging):

# Clean the café tables, skipping table 3 (it is reserved)
table = 1
laps = 0                       # emergency brake ONLY so I can show you the output

while table <= 6:
    laps += 1
    if laps > 8:               # without this brake, the program would never stop
        print("... and so on until you press Ctrl + C")
        break
    if table == 3:
        print("Table 3 reserved, I skip it")
        continue               # goes back up WITHOUT reaching table += 1
    print(f"Cleaning table {table}")
    table += 1

Output:

Cleaning table 1
Cleaning table 2
Table 3 reserved, I skip it
Table 3 reserved, I skip it
Table 3 reserved, I skip it
Table 3 reserved, I skip it
Table 3 reserved, I skip it
Table 3 reserved, I skip it
... and so on until you press Ctrl + C

See the problem? When table is 3, continue jumps back to the top of the loop without reaching table += 1. The table is still 3, the condition table <= 6 is still true, and the program is trapped forever in the same pass. Without the brake, the only way out was pressing Ctrl + C.

That day I understood what a loop really does, and that is what this article is about. We come from Python conditionals, where the program learned to decide. Now it will learn to repeat: the for and while loops, range(), break, continue, the “do while” Python doesn’t have, nested loops and the patterns you will write in every FP1 exercise. All with one running example: the faculty café till (the same one from Python operators). When you finish, the loops practice builds three complete programs.

What is a loop in Python?

A loop is a block of code that repeats. Without loops, adding up a morning’s tickets would mean one line per ticket; with a loop you write the addition once and Python repeats it as many times as needed. Each repetition is called a pass (or iteration).

Python has two loops, and the difference between them is the question they ask before each pass:

  • for asks “is there another item?”. It walks through a sequence (a list, a text, a range) and makes one pass per item. You know in advance how many passes there will be.
  • while asks “does the condition still hold?”. It keeps going while the condition is True, without knowing how many passes that will take.

Just like with conditionals, the header ends with a colon and the loop body is indented: everything indented below the for or while is repeated; whatever goes back to the left comes after the loop. Look at the diagram:

How for and while workTwo flowcharts. The for loop asks whether there are items left, runs the body with the next one and goes back. The while loop checks the condition, runs the body, must update the condition variable and goes back; when the answer is no, the program goes on.for: walks through a sequencefor ticket in tickets:tickets left?yesbody with ticket = 3.50,then 1.20, then 4.80…the for moves on by itselfnothe program goes onwhile: repeats while it holdswhile queue > 0:queue > 0?yesloop bodyqueue -= servedthis changes the condition!nothe program goes on
Both go back up after each pass; the difference is who moves the loop forward: the for does it by itself, in the while you do.

Both loops do essentially the same thing: check, run the body, go back up. The difference is the coral box: in a while, you have to change something inside the body so the condition eventually becomes false. If you don’t, you get my table 3.

“loop”, “cycle” and “iteration” are often used interchangeably. In the FP1 notes you will also see “counter-controlled iteration” (the for) and “condition-controlled iteration” (the while).

The for loop

A for takes a sequence and gives a name to each item, one after another. On the first pass, the loop variable holds the first item; on the second, the second; and so on until the end. Let’s go with a morning’s tickets at the café:

# Morning tickets at the faculty café (in euros)
tickets = [3.50, 1.20, 4.80, 2.30, 6.10, 1.20]

total = 0                           # accumulator: starts at 0 BEFORE the loop
big = 0                             # counter: tickets over 4 €
highest = tickets[0]                # maximum: starts with a real ticket, not a made-up number

for ticket in tickets:              # ticket is 3.50, then 1.20, then 4.80...
    total += ticket                 # adds this ticket to what we had so far
    if ticket > 4:
        big += 1                    # only counts the ones that meet the condition
    if ticket > highest:
        highest = ticket            # a higher one showed up: we remember it

print(f"Tickets: {len(tickets)}")
print(f"Morning total: {total:.2f} €")
print(f"Average per ticket: {total / len(tickets):.2f} €")
print(f"Tickets over 4 €: {big}")
print(f"Highest ticket: {highest:.2f} €")

Output:

Tickets: 6
Morning total: 19.10 €
Average per ticket: 3.18 €
Tickets over 4 €: 2
Highest ticket: 6.10 €

Three things happen inside the same loop, and all three are patterns you will repeat a thousand times:

  • Accumulator (total): starts at 0 before the loop, is updated inside (total += ticket) and is used after.
  • Counter (big): also starts at 0, but only adds 1 when a condition is met.
  • Maximum (highest): starts with a real value from the list and is replaced every time a higher one shows up.

So that nobody has to imagine anything, here is the pass-by-pass trace (values after each pass):

Passtickettotalbighighest
before—003.50
13.503.5003.50
21.204.7003.50
34.809.5014.80
42.3011.8014.80
56.1017.9026.10
61.2019.1026.10

Checking by hand: 3.50 + 1.20 + 4.80 + 2.30 + 6.10 + 1.20 = 19.10 €, and 19.10 / 6 = 3.18 €. There are two tickets over 4 € (4.80 and 6.10). It matches the output.

Putting total = 0 inside the loop. Then it resets on every pass and only the last ticket survives. You will see it live in the exercises, in “What’s wrong here?”.

You choose the name of the loop variable. for ticket in tickets reads almost like a sentence (“for each ticket in the tickets”). Use the singular of the list and your code will explain itself.

Change coin by coin, now with a for

In the operators exercises we worked out the change at the till with twelve almost identical lines, one per note or coin (b50, rest = divmod(rest, 5000), b20, rest = divmod(rest, 2000)…). I promised you loops would do it better. Here it is: the twelve values go in a list and one single divmod line is repeated twelve times.

# Change coin by coin (exercise 5 of Operators), now with a loop
amount = 37.43
handed = 50.00
remainder = round((handed - amount) * 100)           # WHOLE cents: no float errors this way
total_cents = remainder                              # we keep it for the title
values = [5000, 2000, 1000, 500, 200, 100, 50, 20, 10, 5, 2, 1]   # notes and coins, in cents
pieces = 0

print(f"--- Change: {total_cents // 100}.{total_cents % 100:02d} € ---")
for value in values:                                 # largest to smallest: so the biggest pieces are used
    how_many, remainder = divmod(remainder, value)   # how many fit and what is left for the next one
    if how_many > 0:                                 # we only show what is actually handed over
        if value >= 100:
            name = f"{value // 100} €"
        else:
            name = f"{value} cent"
        print(f"{how_many} x {name}")
        pieces += how_many
print(f"Pieces: {pieces} | left over: {remainder} cents")

Output:

--- Change: 12.57 € ---
1 x 10 €
1 x 2 €
1 x 50 cent
1 x 5 cent
1 x 2 cent
Pieces: 5 | left over: 0 cents

Let’s zoom into the heart of the program, the loop:

for value in values:                                  # largest to smallest: so the biggest pieces are used
    how_many, remainder = divmod(remainder, value)    # how many fit and what is left for the next one
    if how_many > 0:                                  # we only show what is actually handed over
        if value >= 100:
            name = f"{value // 100} €"
        else:
            name = f"{value} cent"
        print(f"{how_many} x {name}")
        pieces += how_many

On each pass, value is a note or a coin (in cents) and divmod(remainder, value) answers two questions at once: how many pieces of that value fit into what is left (how_many) and how much is left over for the next ones (remainder). Since remainder is overwritten on every pass, the next coin only works with what was left. This is the trace of the passes where something happens:

valueremainder beforedivmodhow_manyremainder after
5000, 20001257(0, 1257)01257
10001257(1, 257)1257
500257(0, 257)0257
200257(1, 57)157
10057(0, 57)057
5057(1, 7)17
20, 107(0, 7)07
57(1, 2)12
22(1, 0)10
10(0, 0)00

Check: 10 € + 2 € + 0.50 € + 0.05 € + 0.02 € = 12.57 €, which is 50.00 − 37.43. Five pieces and nothing left over.

The order of the list matters. Going from largest to smallest, each pass uses the biggest possible piece, which is why so few pieces come out. If the list went from smallest to largest, the first pass (1 cent) would take everything: 1257 one-cent coins.

The for loop with range()

Often you don’t have a list but a number: “repeat this 6 times”, “go through tables 1 to 10”. That is what range() is for: it produces whole numbers. It has three forms:

  • range(stop): from 0 to stop − 1.
  • range(start, stop): from start to stop − 1.
  • range(start, stop, step): in steps of step; with a negative step, it counts down.

The best way to understand it is to see it. list() turns the range into a list just to print it (you don’t need it in a for):

# list() is only here to SEE the numbers each range produces
print("range(5)        ->", list(range(5)))
print("range(1, 6)     ->", list(range(1, 6)))
print("range(0, 11, 2) ->", list(range(0, 11, 2)))
print("range(5, 0, -1) ->", list(range(5, 0, -1)))
print("range(5, 1)     ->", list(range(5, 1)))   # empty: going down needs a negative step

Output:

range(5)        -> [0, 1, 2, 3, 4]
range(1, 6)     -> [1, 2, 3, 4, 5]
range(0, 11, 2) -> [0, 2, 4, 6, 8, 10]
range(5, 0, -1) -> [5, 4, 3, 2, 1]
range(5, 1)     -> []

The rule that prevents the most bugs: the stop is never included. range(1, 6) gets to 5, not 6. And the last one comes out empty because, without a negative step, range can only go up: there is no number going “up” from 5 to 1, so a for over that range would not make a single pass (no error, it just does nothing). The diagram makes it clear:

Which numbers range producesThree number lines from 0 to 11. range(1, 11) marks 1 to 10 and leaves out 11. range(10, 0, -1) marks 10 down to 1 and leaves out 0. range(0, 11, 2) marks 0, 2, 4, 6, 8 and 10.range(1, 11) → from 1 to 1001234567891011stop: left outrange(10, 0, -1) → from 10 down to 10stop: left out1234567891011range(0, 11, 2) → two by two01234567891011
The second number of range is a boundary: the loop stops right BEFORE reaching it.

Wanting 1 to 10 and writing range(1, 10). You will miss the 10. If you want to reach N, the stop is N + 1: range(1, 11). It’s the famous off-by-one error, and it causes half of the loop bugs in FP1.

Generating tables with a for loop

A classic use of range is generating tables, like the multiplication table. At the café, the price table for the board:

# Price table for the board: 1 to 6 coffees at 1.20 € each
price = 1.20

print("Cups  | Total")
for cups in range(1, 7):         # 7, not 6: range's stop is NOT included
    print(f"  {cups}   | {cups * price:.2f} €")

Output:

Cups  | Total
  1   | 1.20 €
  2   | 2.40 €
  3   | 3.60 €
  4   | 4.80 €
  5   | 6.00 €
  6   | 7.20 €

cups takes the values 1, 2, 3, 4, 5 and 6 (7 is left out), and on each pass we compute cups * price. For example, 3 × 1.20 = 3.60 €. The :.2f rounds to two decimals when displaying, which is why you don’t see the 3.5999999999999996 the real float holds (if that sounds odd, we explain it in variables and data types).

The while loop

A while doesn’t walk through anything: it repeats while its condition is true. Before each pass it evaluates the condition; if it is True, it runs the body and asks again; if it is False, it moves on to whatever comes next. The café queue at 10:30 is a perfect case:

# The café queue at 10:30: 2 people are served every minute
queue = 7                       # people waiting
minute = 0

while queue > 0:                # while someone is still in the queue
    minute += 1
    served = min(2, queue)      # on the last pass there may be only 1 left
    queue -= served             # the condition depends on queue, and it changes HERE
    print(f"Minute {minute}: serving {served}, left: {queue}")

print(f"Queue empty in {minute} minutes")

Output:

Minute 1: serving 2, left: 5
Minute 2: serving 2, left: 3
Minute 3: serving 2, left: 1
Minute 4: serving 1, left: 0
Queue empty in 4 minutes

The condition is queue > 0, and what makes it change is queue -= served. Here is the trace:

Passqueue > 0? (before)servedqueue after
17 > 0 → True25
25 > 0 → True23
33 > 0 → True21
41 > 0 → True10
—0 > 0 → False—(exits)

Why min(2, queue) and not a fixed 2? Because on the last pass there is only one person left. With a fixed 2, the queue would go from 1 to −1; the condition queue > 0 would end just the same (−1 is not greater than 0), but the program would say “serving 2” with only one person there. min returns the smaller of the two: it serves 2 if there are 2 or more, and whoever is left otherwise.

Before writing a while, answer three questions: which variable does the condition look at? What value does it have before the first pass? Which line in the body moves it towards the end? If you can’t find the third one, you have an infinite loop.

The most dangerous while mistake: infinite loops

If nothing inside the body changes the condition, the while never ends. That’s what happened to me in the opening story. The three most common ways to get there:

  1. Forgetting to update the variable: a while queue > 0: without the queue -= ….
  2. A continue that skips the update: my table 3. The continue jumps back up before reaching table += 1.
  3. Updating in the wrong direction: queue += 1 instead of queue -= 1. The queue grows and never reaches 0.

If it happens to you, press Ctrl + C in the terminal (in VS Code, click on the terminal first): Python raises a KeyboardInterrupt and the program stops. And the fix for my opening program: either move table += 1 above the continue, or, better, use a for, which moves on by itself:

# The same task with for: for moves on by itself, so there is no += 1 to forget
for table in range(1, 7):          # tables 1 to 6 (7 is left out)
    if table == 3:
        print("Table 3 reserved, I skip it")
        continue                   # here continue is safe: for moves on to table 4
    print(f"Cleaning table {table}")

Output:

Cleaning table 1
Cleaning table 2
Table 3 reserved, I skip it
Cleaning table 4
Cleaning table 5
Cleaning table 6

A continue inside a while placed before the line that updates the counter. In a for, continue is harmless (the for moves to the next item on its own); in a while, if it skips the update, the loop never advances.

When to use for vs while

It’s the million-dollar question, and the answer is short: do you know how many passes there will be before you start? If you do (the tickets in a list, tables 1 to 6, the 12 notes and coins), for. If you don’t (until the queue is empty, until the customer types a valid amount), while.

SituationLoopWhy
Go through the tickets in a listfor ticket in tickets:There are as many passes as tickets
Repeat something N timesfor i in range(N):The number of passes is N
Serve until the queue is emptywhile queue > 0:You don’t know how many minutes it will take
Ask for data until it’s validwhile True: + breakYou don’t know how many times they’ll get it wrong
Search for something and stop when foundfor + breakYou walk a sequence, but can leave early

Anything a for does can be done with a while (keeping the counter yourself), but not the other way round. The example from your FP1 notes is Euclid’s algorithm for the greatest common divisor (GCD). At the café: there are 84 croissants and 36 juices and we want to share them on equal trays with nothing left over. What is the largest number of trays?

# 84 croissants and 36 juices on equal trays, nothing left over: how many trays?
# It is the greatest common divisor (Euclid's algorithm, the one in the FP1 notes)
a = 84
b = 36
lap = 0

while b > 0:                          # we don't know how many passes it will take: hence while
    lap += 1
    remainder = a % b
    print(f"Pass {lap}: {a} % {b} = {remainder}")
    a = b                             # the divisor becomes the dividend
    b = remainder                     # and the remainder, the new divisor

print(f"GCD = {a}: {a} trays with {84 // a} croissants and {36 // a} juices")

Output:

Pass 1: 84 % 36 = 12
Pass 2: 36 % 12 = 0
GCD = 12: 12 trays with 7 croissants and 3 juices
Passaba % ba, b after
184361236, 12
23612012, 0
—120—b > 0 is False: exits

How many passes would it take with 1071 and 462? Or with 89 and 55? You don’t know until you run it: working it out would cost as much as running the algorithm. That’s why a for makes no sense here and a while does. Check: 12 × 7 = 84 and 12 × 3 = 36.

break: leaving the loop early

break ends the loop on the spot, even if there are items left or the condition is still true. The program continues at the first line after the loop. It’s mostly used for searches: once you’ve found what you were looking for, why keep looking?

# Which is the first ticket over 5 €? As soon as I find it, I stop looking
tickets = [3.50, 1.20, 4.80, 6.10, 2.30, 7.40]

for ticket in tickets:
    print(f"Checking {ticket:.2f} €")
    if ticket > 5:
        print(f"Found: {ticket:.2f} €")
        break                     # leaves the for: 2.30 and 7.40 are not even checked

print("End of the search")

Output:

Checking 3.50 €
Checking 1.20 €
Checking 4.80 €
Checking 6.10 €
Found: 6.10 €
End of the search

The loop checks 3.50, 1.20 and 4.80 (none is over 5), finds 6.10 and break cuts it off: 2.30 and 7.40 are never checked, not even their “Checking” line is printed. Notice that 7.40 is also over 5 €, but it doesn’t appear: break keeps the first one.

continue: skipping one pass

continue doesn’t leave the loop: it abandons the current pass and goes straight to the next one. Whatever is below the continue is not run on that pass. At the till, cancelled tickets are marked with 0 and must not be added:

# Add up only the valid tickets (0 = cancelled ticket)
tickets = [3.50, 0, 4.80, 0, 2.30]
total = 0

for ticket in tickets:
    if ticket == 0:
        continue                  # skips the rest of THIS pass and goes to the next ticket
    total += ticket
    print(f"Adding {ticket:.2f} €, running total {total:.2f} €")

print(f"Valid total: {total:.2f} €")

Output:

Adding 3.50 €, running total 3.50 €
Adding 4.80 €, running total 8.30 €
Adding 2.30 €, running total 10.60 €
Valid total: 10.60 €

On passes 2 and 4 (ticket == 0), the continue skips total += ticket and the print, which is why there are only three “Adding” lines. Check: 3.50 + 4.80 + 2.30 = 10.60 €.

continue is handy for “filtering” at the start of the body: you discard the cases you don’t care about and the rest of the code doesn’t need extra indentation. You can write the same thing with an if ticket != 0: wrapping the rest; pick whichever reads better.

break vs continue: the key difference

Both words interrupt the current pass, but one leaves the loop and the other stays. With the same loop and the same table 4, look at the difference:

# Table 4 has a problem: what changes between break and continue?
print("Using break:   ", end="")
for table in range(1, 7):
    if table == 4:
        break                     # the loop ends: 5 and 6 never come
    print(table, end=" ")
print()

print("Using continue:", end=" ")
for table in range(1, 7):
    if table == 4:
        continue                  # only 4 is skipped: 5 and 6 do come
    print(table, end=" ")
print()

Output:

Using break:   1 2 3 
Using continue: 1 2 3 5 6 
Ends the current pass?Ends the loop?Then Python goes to…
breakYesYesthe first line after the loop
continueYesNothe next pass (next item, or check the condition)

The empty print() at the end of each loop only adds the line break: inside the loop we print with end=" " so the numbers stay on the same line.

Is there a do while in Python? while True and break

Other languages (C, Java) have do … while: a loop that runs the body first and checks the condition afterwards, so it always makes at least one pass. Python doesn’t have do while. What you do instead is a while True: (a loop that, on its own, never ends) with a break inside at the moment the exit condition is met. That’s exactly what the card machine at the till does when it asks for the amount until it’s valid.

Input validation with while

First, the classic way. To show you the real output without input(), the typed list stands for what the customer types, in order:

# Validate an amount: ask again while it is not valid
# (no input(): the list stands for what the customer types, in order)
typed = [-2.0, 0.0, 4.80]
i = 0

amount = typed[i]            # first read, BEFORE the loop
while amount <= 0:           # while the data is not valid...
    print(f"{amount:.2f} € is not valid, type it again")
    i += 1
    amount = typed[i]        # ...it is read again INSIDE the loop

print(f"Charging {amount:.2f} €")

Output:

-2.00 € is not valid, type it again
0.00 € is not valid, type it again
Charging 4.80 €

It works, but notice the read (amount = typed[i]) appears twice: once before the loop, to have something to check, and once inside, to ask again. With input() it would be the same: one input() before the while and another inside. With while True the read appears only once:

# The same with while True + break: Python's "do while"
typed = [-2.0, 0.0, 4.80]
i = 0

while True:                  # the condition never stops: the way out is the break
    amount = typed[i]        # a single read, inside the loop
    i += 1
    if amount > 0:
        break                # valid data: we leave
    print(f"{amount:.2f} € is not valid, type it again")

print(f"Charging {amount:.2f} € (reads: {i})")

Output:

-2.00 € is not valid, type it again
0.00 € is not valid, type it again
Charging 4.80 € (reads: 3)
Passamountamount > 0?What happens
1-2.0Noprints the warning and goes back up
20.0Noprints the warning and goes back up
34.8Yesbreak: leaves the loop

This also answers “how do I end a while in Python?”: either the condition becomes false, or a break cuts it from inside (or Ctrl + C, if something went wrong).

A while True: with no reachable break. Always check there is a path to the break: if the if guarding it can never be true, you’ve written an infinite loop on purpose.

In a real program with input(), the pattern looks like this: while True: → text = input("Amount: ") → if valid, break → if not, warning. You will see it in the FP1 programs that ask the user for data.

Nested loops: a loop inside another

A nested loop is a loop inside the body of another. The rule that explains everything: the inner loop runs completely on every pass of the outer one. Before the café opens, the chairs need checking: 3 tables with 4 chairs each.

# Check the chairs: 3 tables with 4 chairs each
step = 0

for table in range(1, 4):         # outer loop: tables 1, 2 and 3
    for chair in range(1, 5):     # inner loop: chairs 1 to 4 of THAT table
        step += 1
        print(f"Step {step:2}: table {table}, chair {chair}")

print(f"Total: {step} chairs checked (3 x 4)")

Output:

Step  1: table 1, chair 1
Step  2: table 1, chair 2
Step  3: table 1, chair 3
Step  4: table 1, chair 4
Step  5: table 2, chair 1
Step  6: table 2, chair 2
Step  7: table 2, chair 3
Step  8: table 2, chair 4
Step  9: table 3, chair 1
Step 10: table 3, chair 2
Step 11: table 3, chair 3
Step 12: table 3, chair 4
Total: 12 chairs checked (3 x 4)

table is 1 and, with it fixed, chair goes through 1, 2, 3 and 4. When the inner loop finishes, the outer one makes its second pass (table = 2) and the inner one starts again from chair 1. That’s why there are 3 × 4 = 12 steps. Look at the order in the diagram:

Order of a nested loopA grid of 3 tables by 4 chairs with the step number in each cell: table 1 takes steps 1 to 4, table 2 steps 5 to 8 and table 3 steps 9 to 12.for table in range(1, 4): → for chair in range(1, 5):chair 1chair 2chair 3chair 4table 1step 1step 2step 3step 4table 2step 5step 6step 7step 8table 3step 9step 10step 11step 12outer: 3 passes · inner: 4 passes for each one · 3 × 4 = 12 steps
The inner loop finishes its whole row before the outer one moves on to the next table.

The {step:2} in the print reserves two spaces for the number, which is why “Step 1” has an extra space and the column lines up with “Step 10”.

Star pattern with nested loops

The asterisk triangle is an exam classic because it forces you to understand that the inner loop can depend on the outer one:

# The asterisk triangle, an exam classic
rows = 5

for row in range(1, rows + 1):  # row is 1, 2, 3, 4 and 5
    for _ in range(row):        # as many passes as the row number
        print("*", end="")      # end="" keeps the asterisks on the same line
    print()                     # at the end of the row, line break

Output:

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

On row 1 the inner loop makes 1 pass; on row 2, 2 passes… because its range is range(row). The end="" avoids the line break after each asterisk, and the print() at the end of each row (outside the inner loop but inside the outer one: look at the indentation) does the line break. Using _ as a variable name is a convention: “I need passes, but I don’t use the value”.

In Python, "*" * row repeats the text: "*" * 3 is "***". You can build the triangle with a single loop and print("*" * row). In exams, though, they usually ask for the two-loop version: practise both.

break in nested loops: it only leaves the inner one

Your notes say it in a sentence worth underlining: break only ends the innermost loop it’s in. The outer one carries on as if nothing happened. Let’s check the chairs looking for the first broken one at each table (the broken ones are chair 3 at table 1 and chair 2 at table 3):

# Find the first broken chair at each table (broken: table 1 chair 3 and table 3 chair 2)
for table in range(1, 4):
    for chair in range(1, 5):
        if (table == 1 and chair == 3) or (table == 3 and chair == 2):
            print(f"Table {table}: chair {chair} broken, moving to the next table")
            break                 # leaves ONLY the chairs loop
        print(f"Table {table}, chair {chair}: OK")

print("Check finished")

Output:

Table 1, chair 1: OK
Table 1, chair 2: OK
Table 1: chair 3 broken, moving to the next table
Table 2, chair 1: OK
Table 2, chair 2: OK
Table 2, chair 3: OK
Table 2, chair 4: OK
Table 3, chair 1: OK
Table 3: chair 2 broken, moving to the next table
Check finished

At table 1, break cuts the chairs loop at chair 3 (chair 4 isn’t checked), but the tables loop moves on to table 2, which is checked completely. Here that’s exactly what we want: “the first broken chair at each table“. If you wanted to stop everything at the first broken chair, you’d need a flag variable; we solve it in the exercises.

The accumulator: the essential loop pattern

If you look back over this article, almost every program has the same shape: a variable prepared before the loop, updated inside and used after. That’s the accumulator, and its starting value depends on the operation:

I want to…Before the loopInside the loopToday’s example
Addtotal = 0total += ticketmorning total
Countbig = 0big += 1 (inside an if)tickets over 4 €
Multiplyproduct = 1product *= ifactorial (in the practice)
Find the maximumhighest = tickets[0]if ticket > highest: highest = tickethighest ticket
Build a texttext = ""text += …the “1 x 2 x 3” in the practice

Why does the product start at 1 and not 0? Because 0 × anything = 0: the result would always be 0. 1 is the “neutral” value for multiplication, just as 0 is for addition.

Common loop mistakes

A while that doesn’t update its condition variable, or a continue that skips the update. Infinite loop: Ctrl + C.

range(1, 10) when you wanted to reach 10. The stop is never included: range(1, 11).

Initialising the accumulator inside the loop (it resets every pass) or not initialising it at all (NameError: name 'total' is not defined).

Expecting break to leave all the nested loops. It only leaves the innermost one.

Wrong indentation. A print with the same indentation as the body is repeated on every pass; one aligned with the for runs only once, at the end. If the output comes out repeated (or not at all), check the indentation first.

Watch it step by step in Python Tutor

pythontutor.com runs your code line by line and draws every variable with its value at that moment. With loops it’s gold, because you see the arrow jump back to the top on every pass. Choose Python 3, paste this program (it combines continue and break in the same loop) and click Visualize Execution:

# For Python Tutor: continue and break in the same loop
tickets = [3.50, 0, 4.80, 6.10, 2.30]
total = 0

for ticket in tickets:
    if ticket == 0:
        continue                  # cancelled ticket: on to the next pass
    total += ticket
    if total > 10:
        break                     # till full for today: that's it
print(round(total, 2))

Output:

14.4

What to watch step by step:

  1. On the first pass, ticket is 3.5 and total goes from 0 to 3.5. Notice the red arrow goes back to the for line after the if total > 10.
  2. On the second pass, ticket is 0: the arrow jumps from the continue straight to the for, without going through total += ticket. total stays at 3.5.
  3. On the third, total reaches 8.3 (3.50 + 4.80), still not over 10.
  4. On the fourth, total reaches 14.4 and break sends the arrow out of the loop: the 2.30 ticket never becomes ticket.
  5. At the end, 14.4 is printed. Check: 3.50 + 4.80 + 6.10 = 14.40. The round(total, 2) is there just in case: with other tickets, adding floats could leave something like 14.399999999999999.

Summary

ConceptWhat forExample
for x in sequence:One pass per itemfor ticket in tickets:
range(start, stop, step)Produce whole numbers; stop is left outrange(1, 11) → 1…10
while condition:Repeat while it’s Truewhile queue > 0:
Infinite loopThe condition never changesCtrl + C to stop it
breakLeave the loop (only the innermost)find the first ticket over 5 €
continueSkip the rest of the passdon’t add cancelled tickets
while True + breakPython’s “do while”the card machine
Nested loopsThe inner one runs fully on each outer pass3 tables × 4 chairs
AccumulatorBefore, inside, aftertotal = 0 → total += t

Three ideas to take away:

  1. Before writing a loop, ask yourself whether you know how many passes it will make: if you do, for; if you don’t, while.
  2. In every while, find the line that moves the condition towards False. If it doesn’t exist (or a continue skips it), you have an infinite loop.
  3. range stops right before the stop value, and break only leaves the loop it’s in. Those two sentences prevent most exam bugs.

In the Python loops practice we use them in three complete programs: the till PIN, the ways to order the queue and the croissant oven. And once you’ve mastered loops, the next step is Python functions: putting a loop inside a named function so you never have to write it again.

Next step: practicePython loops — practice with the till PIN, the ways to order the queue and the croissant oven (and the attempt that was counted too late)

Did this article help you? Here is how you can support SergioLearns →

Sigue aprendiendo

Leave a Reply

Your email address will not be published. Required fields are marked *