CIS 2020 · Python for Everyone · Module 7 · Study guide

Module 7 review: loops, sections 4.1, 4.3, and 4.5

Chapter 4 moves from a decision that runs once to a decision that repeats. This module covers the while loop that keeps going for as long as a condition holds, the sentinel pattern that lets a loop read input until a special stop value shows up, and the small set of loop algorithms, summing, counting, and finding an extreme value, that appear inside almost every loop you will write this term.

Section 4.1

The while Loop

An if statement runs a block of code at most once. A while loop runs the same block over and over, checking the same condition again before every single pass.

A while loop repeats a block of indented statements for as long as its condition evaluates to True. The condition is tested before the body ever runs, and again before every repeat, which is why a while loop is called a pretest loop: if the condition is False the very first time, the body never executes at all, not even once.

Anatomy of a while loop Start, then initialize the loop variable, then a decision diamond asking whether the condition is true. The yes branch runs the loop body and updates the loop variable, then loops back up into the same decision. The no branch exits to end. Start Initialize: count = 1 count <= 5 ? (checked every time) Yes print(count) count = count + 1 loop back No End
Every while loop has the same three moving parts: something initialized before the diamond, the diamond's condition, and something inside the loop body that changes the value the diamond is testing. Leave any one of them out and the loop either never runs or never stops.

The three parts, in code

The flowchart above is this exact program. Notice the loop variable count appears in all three places the diagram calls out: it is set once before the loop, tested in the while line, and changed once per pass through the body.

count = 1                 # 1. initialize

while count <= 5 :        # 2. condition, tested before every pass
    print(count)
    count = count + 1      # 3. update, moves toward making the condition False

print("Done")

Forget the update and the loop never ends. If the line count = count + 1 were deleted, count would stay 1 forever, count <= 5 would always be True, and the program would print 1 endlessly. This is an infinite loop, and it is the single most common mistake beginners make with while. Before running any loop you write, ask: "what in this body makes the condition eventually False?"

A loop whose number of passes is known in advance, like the one above, is called a definite loop. A loop that depends on something not known ahead of time, like user input, is an indefinite loop. Section 4.3 is built entirely out of indefinite loops.

Three more ways a while loop can misbehave

"Forgot the update" is not the only way to write an infinite loop, and these three show up constantly in trace-the-code questions:

year = 0
rate = 5
principal = 10000

while year < 10 :
    interest = (principal * year * rate) / 100
    print("Interest", interest)
    # year is never updated here -- infinite loop,
    # even though interest changes every single pass

When tracing code, check the exact variable in the while line, not just whether something is changing. It is easy to see a line like interest = … changing every pass and assume the loop is making progress. Progress only counts if it is progress toward making that specific condition False.

When the loop body changes its own loop variable

A loop variable does not have to change only on the update line at the bottom. An if inside the body can reassign it too, which lets a loop skip ahead or cut itself short early:

i = 1
while i < 10 :
    print(i, end = " ")
    i = i + 2
    if i == 5 :
        i = 9
Tracing i through every pass. The jump happens the moment i becomes exactly 5, which only the if catches.
Passi at printi after i = i + 2if i == 5 fires?
113no
235yes → i = 9
3911no — loop exits, 11 < 10 is False

Output: 1 3 9. Notice 5 and 7 never print; the if jumped i from 5 straight to 9 before the next check. The lesson is the same as above: trace the condition's exact variable after every line that could touch it, including lines inside an if, not only the update line you expect.

Tracing the condition itself

Because the test happens before the body, the loop checks count <= 5 one extra time, at count == 6, and that is the check that finally stops it. Tracing every single check, not just the ones that pass, is the easiest way to catch an off-by-one mistake before it ever becomes a bug:

Tracing count through the loop, one check at a time Six boxes in a row, count from 1 through 6. The first five show the condition count less than or equal to 5 as true, and print that value. The sixth box shows the condition as false and exits the loop instead of printing. count = 1 1<=5 → True prints 1 count = 2 2<=5 → True prints 2 count = 3 3<=5 → True prints 3 count = 4 4<=5 → True prints 4 count = 5 5<=5 → True prints 5 count = 6 6<=5 → False exits
The loop body only runs five times, but the condition itself is checked six times. The sixth check is the one that stops the loop, and it never prints anything.

A loop that accumulates, not just counts

The loop variable does not have to be the thing you print. Here, number plays the same three-part role as count did above, while a second variable, total, builds up a running sum one pass at a time:

total = 0
number = 1

while number <= 10 :
    total = total + number
    number = number + 1

print("Sum:", total)
Tracing total and number through every pass of the loop above.
Passnumbertotal after this pass
111
223
336
4410
5515
………
101055

This pattern, a variable that starts at 0 and gets added to once per pass, is called an accumulator. It comes back constantly in Section 4.5.

Looping over a string by index

A string's characters can be visited the same way: an index variable plays the loop-variable role, and s[i] pulls out one character per pass. Forward, starting the index at 0 and stopping before len(s), visits every character; starting the index at len(s) - 1 and stopping once it drops below 0 visits them in reverse:

s = "abcde"
i = 0
while i < len(s) :         # forward
    print(s[i])
    i = i + 1
# prints a b c d e

j = len(s) - 1
while j >= 0 :              # backward
    print(s[j])
    j = j - 1
# prints e d c b a

Starting the index at 1 instead of 0 silently skips the first character. s[0] is always the first character of a string, never s[1]. A forward loop that starts its index at 1 prints every character except the first one, and nothing about it looks wrong at a glance, which is exactly why trace-the-code questions like to start the index one too high.

Section 4.3

Application: Processing Sentinel Values

The loops in Section 4.1 all knew their count in advance: five rides, ten numbers. A sentinel loop is for when you have no idea how many inputs are coming, and the user tells the program when to stop.

A sentinel value is a special input value that is not real data at all; it exists only to signal "there is nothing more to process." A sentinel has to be something that could never be a legitimate value in the actual data, such as -1 for a list of quiz scores that are always 0 or higher, or an empty string for a list of names.

Sentinel loop: the priming read pattern Start, read the first score before the loop begins, a priming read. Decision diamond asks whether the score equals the sentinel, negative one. Yes exits straight to printing results and end. No adds the score to the running total, reads the next score, and loops back up into the same decision. Start Read first score (priming read) score == -1 ? Yes Print total and count End No total = total + score Read next score loop back
Two reads, not one: a priming read before the loop decides whether the loop body runs at all, and a second read at the bottom of the loop body decides whether it runs again.

Why the value gets read twice

The read before the loop is called a priming read, and it exists so the while condition has something to test on its very first check. Without it, the condition would have nothing to compare on pass zero. The second read, at the bottom of the loop body, replaces that value with the next one, which is exactly what the condition checks the next time around.

total = 0
count = 0

score = int(input("Enter a score (-1 to stop): "))   # priming read

while score != -1 :
    total = total + score
    count = count + 1
    score = int(input("Enter a score (-1 to stop): "))  # read next

print("Total:", total)
print("Count:", count)

Forget the second read and the loop never stops. If the line inside the loop that reads the next score is deleted, score keeps the same value every pass, the condition never becomes False on its own, and the loop runs forever on whatever the first value was. The update in a sentinel loop is the read.

A sentinel does not have to be a number

Any value that can never legitimately appear in the data works as a sentinel, including an empty string. Pressing Enter with no text produces "", which makes a natural stop signal for a loop that collects names:

name = input("Enter a name (press Enter to stop): ")   # priming read
count = 0

while name != "" :
    count = count + 1
    print("Hello,", name)
    name = input("Enter a name (press Enter to stop): ")  # read next

print("Greeted", count, "people")

A negative number is not automatically a safe sentinel. -1 only works as a sentinel for quiz scores because a real score can never be negative. If the data itself can legitimately include negative numbers, such as temperatures or account balances, no number is safe to use as a sentinel; a non-numeric value, like a blank line or the letter Q, is the only reliable choice.

A sentinel does not have to be a single exact value

The stop condition can also be a general rule instead of one magic number. A loop collecting only positive scores can simply stop the moment a value fails to be positive, letting the condition that defines "real data" double as the sentinel check:

total = 0
count = 0

value = int(input("Enter a positive value (0 or less stops): "))

while value > 0 :
    total = total + value
    count = count + 1
    value = int(input("Enter a positive value (0 or less stops): "))

Here anything that is not positive ends the loop, not just one specific number. The logic is identical to the -1 version above, just phrased as a range instead of an exact match.

Controlling a sentinel loop with a flag variable

Instead of testing the sentinel directly in the while line, some programs use a separate bool variable, often named done, that starts False and gets explicitly set to True once the stop condition is detected inside the loop. The loop shape is while not done : instead of while value != sentinel ::

done = False

while not done :
    letter = input("Enter a letter (Q to stop): ")
    if letter == "Q" :
        done = True
    else :
        print("You entered:", letter)

A flag loop still needs the same three parts as any other while loop. done is initialized before the loop, tested in the condition, and set inside the body, exactly like count or a sentinel value were in earlier examples. The only difference is what changes the condition: a direct comparison to input versus a flag an if sets explicitly. Forgetting the done = True line produces the same infinite loop as forgetting any other update.

Guarding against a sentinel that never arrives

If there is any chance a sentinel might never show up, such as reading from a file that could be malformed, combining the sentinel check with a maximum count using and keeps the loop from running forever:

count = 0
value = input("Enter a value, Q to quit: ")

while count < 100 and value != "Q" :
    count = count + 1
    value = input("Enter a value, Q to quit: ")

This loop stops whichever comes first: the user typing Q, or 100 values having been entered. Both halves of the and have to still be true for the loop to continue.

Sentinel loops versus validation loops. Section 3.9's input validation loop keeps asking because the input it already received was bad, and stops the moment it becomes good. A sentinel loop is the opposite: every value it reads before the sentinel is already good data, and it stops only when the user deliberately signals there is no more of it. Same loop shape, two different jobs.

Section 4.5

Common Loop Algorithms

Almost every loop you will write this term is a small variation on one of three patterns: adding things up, counting things, and finding the biggest or smallest thing. Learn the shape once and you will recognize it everywhere.

The accumulator pattern in three steps Before the loop, initialize total and count to zero. Each time through the loop, add the value to total and add one to count. After the loop, compute the average from total divided by count. Before the loop total = 0 count = 0 Each time through total = total + value count = count + 1 (repeats) After the loop average = total / count
Every algorithm below follows this same three-box shape: set something up before the loop, update it once per pass inside the loop, then use the final value after the loop ends.

Sum and average

This is the accumulator from Section 4.1, applied to sentinel-terminated input from Section 4.3. total and count are both initialized to 0 before the loop; the average is only computed after the loop, and only if at least one value was actually entered.

total = 0
count = 0

score = int(input("Enter a score (-1 to stop): "))

while score != -1 :
    total = total + score
    count = count + 1
    score = int(input("Enter a score (-1 to stop): "))

if count > 0 :
    average = total / count
    print("Average:", average)
else :
    print("No scores were entered.")

Always guard the division. If the very first input is the sentinel, count stays 0 and total / count raises ZeroDivisionError. The if count > 0 check above exists for exactly that case.

Counting matches

A counting loop adds 1 to a counter only when a condition is met, instead of adding every value unconditionally. The accumulator and the condition from Section 4.3 combine directly:

passingCount = 0

score = int(input("Enter a score (-1 to stop): "))

while score != -1 :
    if score >= 60 :
        passingCount = passingCount + 1
    score = int(input("Enter a score (-1 to stop): "))

print("Passing scores:", passingCount)

Finding a maximum (or minimum)

To track the largest value seen so far, use the first real value as a starting guess, then compare every later value against it, replacing the guess whenever something bigger comes along:

highest = int(input("Enter a score (-1 to stop): "))   # first value is the first guess
score = highest

while score != -1 :
    if score > highest :
        highest = score
    score = int(input("Enter a score (-1 to stop): "))

print("Highest score:", highest)

Finding a minimum is the same algorithm with one symbol flipped. Change if score > highest : to if score < lowest :, and initialize lowest the same way, from the first real value, not from 0. Starting a minimum search at 0 is a common bug: it silently breaks the moment every real value happens to be above zero.

Comparing consecutive values

Some problems care about how a value relates to the one entered right before it, not the whole data set. The trick is a variable, usually named previous, that is set to the old value before the new one overwrites it:

letter = input("Enter a letter (Enter to stop): ")

while letter != "" :
    previous = letter
    letter = input("Enter a letter (Enter to stop): ")
    if letter == previous :
        print("Duplicate entry:", letter)

Order matters here. previous = letter has to happen before letter is overwritten by the next read, or there is nothing left to compare against. Reversing those two lines compares a value to itself, which is always true and defeats the whole point.

Searching until found

A search loop stops as soon as it finds what it is looking for, not after a fixed number of passes. The usual shape pairs a bool flag with a position counter, and the loop condition checks both: keep going while not yet found and still more to check.

s = "abcdEfghI"
position = 0
found = False

while position < len(s) and not found :
    if s[position].isupper() :
        found = True
    else :
        position = position + 1

print("First uppercase letter is at position", position)

Both halves of the condition are doing real work. not found stops the loop the instant a match turns up, so it does not keep scanning past it. position < len(s) stops the loop even if nothing ever matches, so it does not run off the end of the string. Drop either half and the loop breaks: without not found it keeps scanning past every match; without the length check it crashes on a string with no uppercase letter at all.

All five patterns side by side. Each is the same accumulator shape with a different update rule.
GoalBefore the loopEach pass through the loopAfter the loop
Sum / averagetotal = 0, count = 0total += value, count += 1average = total / count
Counting matchesmatchCount = 0if condition: matchCount += 1use matchCount directly
Maximum / minimumbest = first valueif value beats best: best = valueuse best directly
Comparing consecutive valuesread one value firstprevious = current, read next, compare— (acts as it goes)
Searching until foundfound = False, position = 0if match: found = True, else advance positionuse position or found

Combining all three in one pass

Nothing stops a single loop from updating more than one accumulator at a time. This version computes the sum, the count, and the highest score, all from the same sentinel-terminated input, in one trip through the data:

total = 0
count = 0

score = int(input("Enter a score (-1 to stop): "))
highest = score

while score != -1 :
    total = total + score
    count = count + 1
    if score > highest :
        highest = score
    score = int(input("Enter a score (-1 to stop): "))

if count > 0 :
    print("Average:", total / count)
    print("Highest:", highest)

Three bugs these patterns hide in plain sight. input() always returns a str, even when the prompt asks for a number. total = total + value without wrapping the read in int() or float() first raises a TypeError the moment arithmetic is attempted on text, and value > 0 does the same for a comparison. A line that uses a name that was never assigned, such as printing letter when the variable is actually called s[position], raises a NameError instead. And a missing closing parenthesis on a line like value = int(input("Enter a value: ") is a SyntaxError that stops the program before it ever runs a single line. When code "looks right" but the question is about what happens when it runs, check the type of every value coming from input() first.

Quick check

If you remember only these

  • A while loop is a pretest loop: the condition is checked before the body runs, including the final check that stops it. Every while loop needs three parts: initialize, condition, and an update that eventually makes the condition False.
  • A loop whose pass count is known ahead of time is definite; one that depends on something unknown, like user input, is indefinite.
  • Leaving out the update, updating in the wrong direction, or updating a variable the condition never actually checks all produce an infinite loop. So can != paired with a step size that can skip right past the stopping value.
  • A loop variable can be changed by an if inside the body, not just by the update line at the bottom. Trace it after every line that could touch it.
  • A sentinel value is a stop signal, not real data; it must be something that could never legitimately appear in the actual input. A number that is already a possible real value, including a negative one when the data can be negative, is not safe as a sentinel.
  • A sentinel loop needs two reads: a priming read before the loop so the condition has something to test the first time, and a second read at the bottom of the loop body that supplies the next value. A done flag variable set True inside the loop works the same way, just with the stop condition spelled out in an if instead of the while line.
  • Validation loops (Section 3.9) re-ask because the input was bad. Sentinel loops (Section 4.3) stop because the user said to; every value before the sentinel was already good.
  • Sum/average, counting matches, finding a maximum or minimum, comparing a value to the one before it, and searching until found are all the same accumulator shape: set up before the loop, update once per pass, use the result after the loop ends.
  • Guard a division by checking count > 0 before computing an average. Initialize a maximum, minimum, or "previous value" search from the first real value, never from 0.
  • input() always returns a str. Forgetting int() or float() before doing arithmetic or a numeric comparison on it raises a TypeError, not a wrong answer, when the code actually runs.