Skip to content
Courses

Decisions and Repetition

Programming FoundationsLesson 3 of 875 minutesLessonBuilds on: Python Scripts and ValuesNo lab next

The question: which instructions actually ran?

Section titled “The question: which instructions actually ran?”

Our previous script could turn incoming text into an integer, but it treated every successfully converted value the same way. Suppose a score of 17 should be reported as medium and a score of 24 as high. Editing the output string before each run would put the decision in our hands instead of the program’s. We need the program to compare its input with a rule and choose what to do.

We also don’t want to copy that comparison twenty times to handle twenty scores. Repeating instructions should be part of the program too, with a clear way to decide when the work is finished. Conditions and loops give us both abilities. We’ll follow which statements run and which values change, then compare the resulting output with our prediction.

For the short examples before classify.py, use a separate control-practice.py in your programming exercise directory. Replace its contents with each complete example and run python3 control-practice.py. That keeps an earlier loop or assignment from changing the state of the example you’re trying to understand.

Comparisons like score >= 20 produce True or False. Python also treats values such as 0, "", [], and None as false inside a condition. Nonzero numbers and nonempty strings count as true, including the string "0". That last case is worth checking before you use incoming text as a condition. I’d write the comparison explicitly when the program needs a particular numeric boundary.

score = 17
if score >= 20:
result = "high"
elif score >= 10:
result = "medium"
else:
result = "low"
print(result)

Each test in an if or an elif is a condition, and each indented block guarded by one is a branch. You’ll see both words constantly from here on. Python tests those conditions from top to bottom and runs the first matching branch. The indentation defines which instructions belong to which branch.

For 17, the first comparison is false, so Python skips result = "high". The elif means “otherwise, if”: its comparison is tried only because the earlier one failed. Seventeen is at least ten, so that assignment sets result to "medium". The remaining else is skipped. else supplies a fallback when none of the preceding conditions matched; it has no separate comparison.

The colon ends each branch header, and the indented lines form its body. print(result) is back at the left margin, so it runs after the whole decision, whichever branch was selected. Moving that print into one branch would change when it executes. Indentation is part of the program here, not just an attempt to make it look tidy.

Order matters as much as the comparisons themselves. A score of 24 satisfies both >= 20 and >= 10, but this chain selects only the first match. Two separate if statements would perform two separate tests, potentially assigning result twice. When you read a decision, check whether the source expresses alternatives in one chain or independent operations.

Predict the results for 9, 10, 19, and 20. The values at the edges distinguish a strict comparison from one that includes equality. Testing only 17 would miss that difference.

Nested decisions belong to the selected outer branch:

score = 17
submitted = True
if submitted:
if score >= 10:
result = "passing"
else:
result = "not-passing"
else:
result = "missing"
print(result)

Trace submitted=False before you touch score at all. The inner comparison doesn’t run with the outer condition false, and changing a value on an unreachable path can’t affect this execution one way or the other.

Suppose we want the sum of three values. We could write three separate additions, but that would tie the amount of source code to the amount of data. A for loop lets us state the repeated job once and apply it to each element in a collection. One pass through its body is an iteration.

In the example below, the list supplies a new value at the start of each iteration. total has a different role: it preserves the sum accumulated so far. Its initial assignment belongs before the loop, because starting it at zero inside the body would throw away the previous sum on every pass.

total = 0
for value in [3, 5, 7]:
total = total + value
print(value, total)

Build a table with columns for the iteration, value, and total. The final answer matters a lot less here than being able to explain every transition, and the same goes for most debugging.

After making your prediction, run the example. The first addition reads total as zero and value as three, then stores three. The second reads that three and adds five, producing eight. The last adds seven to eight, producing fifteen. The assignment uses the previous total before replacing it. The print sits after the update, so each row reports the new total.

For integer values, total += value is the shorter update notation we’ll use below. Likewise, remaining -= 1 subtracts one and assigns the result back. These statements change state. Merely writing total + value would calculate a result and leave total unchanged.

A while loop repeats while its condition remains true:

Unlike the for loop above, this loop isn’t taking values from a fixed list. It asks the same question before each pass and enters the body only if the answer is true. Something in that body needs to change the answer eventually, unless an explicit exit ends the loop. We’ll use a countdown so both the repeated work and the stopping rule are visible.

remaining = 3
while remaining > 0:
print(remaining)
remaining -= 1

Without the final line, remaining stays at 3. The condition is still true every time Python checks it, so the output keeps coming. Stop the program with Ctrl+C. The resulting KeyboardInterrupt shows roughly where it was executing when you interrupted it.

With the decrement present, the printed values are three, two, and one. After printing one, the body changes remaining to zero. Python returns to the condition, finds 0 > 0 false, and leaves without printing zero. If remaining started at zero, the body wouldn’t run even once. Restore the decrement before continuing if you removed it to observe the failure.

range, boundaries, and explicit loop control

Section titled “range, boundaries, and explicit loop control”

range(start, stop) includes start and excludes stop:

for index in range(0, 4):
print(index)

The stop value is excluded, so range(0, 4) visits 0 through 3. That’s useful for indexing: range(len(items)) visits the valid indexes of a list whose length stays fixed.

With one argument, range(4) starts at zero; with two, range(2, 4) starts at two and still excludes four. It supplies integers to the loop as needed. The loop then assigns each one to index and runs its body. When you need the elements themselves, the earlier for value in values is simpler. Use indexes when the position is part of the question.

continue skips the rest of the current iteration, while break ends the loop entirely (two very different exits):

values = [4, -1, 7, 0, 9]
total = 0
for value in values:
if value < 0:
continue
if value == 0:
break
total += value
print(f"accepted={value} total={total}")
print(f"final={total}")

Predict which values actually reach the addition. Expected output is:

accepted=4 total=4
accepted=7 total=11
final=11

The -1 iteration hits continue before the addition, and the zero iteration breaks before the addition, so 9 never gets visited at all.

Both statements skip the remaining body, but only continue asks for another element. break leaves this loop and resumes at the statement after it, which is why the final print still runs. Neither statement ends the whole Python process here.

The f before each quoted message marks a formatted string. Expressions inside braces are evaluated and inserted into the text: f"final={total}" displays the current total after final=. The braces describe substitutions in the source; they aren’t printed around the number. This lets a trace label the value being observed instead of leaving us to guess what a column means.

Python’s control-flow tutorial has more examples of these exits. Compare what happens after continue with what happens after break.

Save this complete file as classify.py. The list supplies integers directly so we can follow the branches without a conversion failure interrupting the loop.

values = [3, 0, 5, -1, 20]
accepted = 0
total = 0
for value in values:
if value == 0:
print("skip:", value)
continue
if value < 0:
print("stop:", value)
break
accepted += 1
total += value
print("accept:", value, "total:", total)
print("summary:", accepted, total)

Before running python3 classify.py, make a table with one row per value. Check whether the value reaches the addition, the new total, and whether another iteration follows. Then compare the output against your prediction.

Keep accepted and total in separate columns. The first counts records, while the second adds their values. They change together on an accepted record, but by different amounts. A skipped record changes neither; a stopping record prevents later records from reaching the body at all.

If a row surprises you, add a temporary print just before the decision to show the incoming value and current total. Compare it with the existing print after the update. A trace before an assignment answers “what arrived?”, while one after it answers “what changed?”. Remove the temporary print once you’ve explained the discrepancy. Moving the assignment to make the trace look right would change the program we were trying to understand.

Check the trace after your attempt

The program accepts 3, skips 0, accepts 5, then stops at -1. The last value never reaches the loop body. The summary is two accepted values totaling 8. If your total includes 20, check where break ends execution.

Change the accepted interval to 1 through 10. Values above 10 should be reported and skipped; zero should still be skipped, and any negative value should still stop the loop. Add a rejected counter for skipped values.

Check which branch handles 0, 1, 10, and 11, then predict the result for [1, 11, 10, -1, 5]. Run it and follow the first input where your prediction differs. A scratch trace is optional. Preserve the baseline before modifying it so you can restore the working version.

Finish by testing an empty list and a list that starts with a negative value. Both should print a summary without accepting any values. All these runs reach the end of the Python script normally; deliberate nonzero status returns arrive with the function lesson.

  • IndentationError or TabError: a block isn’t aligned consistently. Set the editor to insert spaces, turn on whitespace display if it has it, and align the failing line with its sibling statements.
  • A while loop never ends: press Ctrl+C, then find the variables its condition uses and the paths supposed to change them. Add a one-line trace and shrink the input.
  • The total is one iteration behind: check the state on both sides of the update. Decide deliberately whether the output belongs above or below total += value, rather than moving it around at random.
  • A boundary gets accepted twice, or not at all: write out the interval represented by each if and elif branch. Test the exact endpoints before you start changing operators.

continue skips the rest of the current iteration, while break leaves the loop. Compare the counts in your changed run with the inputs that actually reached the update. If they disagree, follow just the first input where the decision differs.

You can now turn control flow into testable paths, and trace state across repetitions. The next lesson packages behavior up into functions and starts moving data through files.

Knowledge check

Question 1 of 2

In the baseline classify.py, values is [2, 0, 4, -1, 9]. What summary appears?