Questions
What problem does Loops and Tracing help us solve in a small Python program?
What should we predict before running the example?
What value, output, or error should we inspect after changing one line?
Objectives
Run a complete example for loops and tracing in Colab.
Explain the example line by line using plain language.
Change one part of the code and predict the result before running it.
Recognize one common mistake and use the error message as evidence.
Hands-on episode: Loops and Tracing
A loop repeats an indented block. The loop variable takes one value at a time, and any accumulator variable should be traced carefully because it changes across repetitions.
We will learn this by running code, not by memorizing a definition first. Open the Colab notebook from the button above, find this section, and run each cell in order. Keep a small note beside the notebook with three columns: prediction , actual result , and what changed .
Example 1.1
Predict the final value of total, then trace each loop trip.
scores = [4 , 5 , 3 ]
total = 0
for score in scores:
total = total + score
print (total)
Run the cell once without editing it. If the result is different from your prediction, leave the prediction visible and write one sentence about the difference. That sentence is more useful than a perfect first guess.
Explain Example 1.1
Before the loop, total is 0, which means the accumulator starts empty.
On the first trip, score is 4, so total becomes 4.
On the second trip, score is 5, so total becomes 9.
On the third trip, score is 3, so total becomes 12.
Now explain the example out loud or in a Markdown cell. Use short sentences: “this line creates…”, “this name stores…”, “this output appears because…”. If you cannot explain a line yet, run only the lines above it and inspect the values that exist at that moment.
Challenge 1.1
Move total = 0 inside the loop and run again. The total will keep resetting, which is a common accumulator bug. This shows why the position of a line can matter as much as the line itself.
Show a safe way to approach the challenge
Copy Example 1.1 into a new Colab cell.
Change exactly one value, name, condition, or line.
Write the expected output before running the cell.
Run the cell and compare the actual result with your prediction.
If the result surprises you, undo the change and try a smaller one.
Suggested first move: Move total = 0 inside the loop and run again.
Debugging checkpoint 1.1
For while loops, the common danger is forgetting to update the value that controls the loop. If a cell keeps running, stop it safely, then ask: which condition is supposed to become false, and where does the code change that value?
Do not debug by rewriting the whole example. Read the error type or surprising output, inspect the closest value with print(...) or type(...), then change one thing. This is the same routine you will use in larger projects.
Apply it
In Colab, total a list of prices, count how many are above 10, and search for the first price above 100. Use a small trace table before running each version.
Finish by adding a Markdown cell that answers: What did this example teach me that I can reuse in a project?
Key points
Learn the concept by running a complete, small example first.
Predict before execution so your thinking becomes visible.
Change one thing at a time so cause and effect stay clear.
Treat errors as clues about the exact line or value Python could not handle.
The problem this lesson solves
Computers are especially good at repetition. If you can write instructions for one item, a loop can repeat those instructions for many items. Loops help you print every name in a list, add many numbers, search for a match, or keep asking until valid input appears.
The hard part for beginners is not typing for or while; it is tracking what changes each time the loop repeats. This lesson teaches loops together with tracing , the habit of writing down values as the program runs.
Learning goals
By the end, you should be able to:
write a for loop over a list;
write a while loop with a clear stopping rule;
explain the loop variable and the loop body;
use break and continue intentionally;
make a trace table to debug loop behavior.
How loops repeat work
A loop is a repeated path. The important debugging question is: what changes each time around the loop?
flowchart TD
start([Start loop]) --> check{"Another item<br/>or condition true?"}
check -- Yes --> bind["Set loop variable<br/>or inspect condition"]
bind --> body["Run loop body"]
body --> update["Update state<br/>total, count, index, etc."]
update --> check
check -- No --> done([Continue after loop])
Use a trace table beside this diagram: one row for each trip through the loop.
1. for loops repeat once per item
A for loop is best when you already have a collection of items and want to do something with each item.
for item in collection:
do_something_with(item)
Read this as: “for each item in this collection, run the indented block.”
Example 1
Predict the output before running the code.
names = ["Ada" , "Grace" , "Katherine" ]
for name in names:
print (f"Hello, { name} " )
Step-by-step explanation
names = ["Ada", "Grace", "Katherine"]
Python creates a list of three strings.
for name in names:
Python starts the loop.
On the first trip, name refers to "Ada".
print(f"Hello, {name}")
The indented line is the loop body.
It prints Hello, Ada on the first trip.
Python goes back to the top of the loop.
On the second trip, name refers to "Grace".
On the third trip, name refers to "Katherine".
After the last item, the loop ends.
Trace table
1
"Ada"
Hello, Ada
2
"Grace"
Hello, Grace
3
"Katherine"
Hello, Katherine
A trace table makes an invisible process visible.
Building a total
Many loops update a value each time:
scores = [10 , 8 , 9 ]
total = 0
for score in scores:
total = total + score
print (total)
The variable total starts at 0. Each trip through the loop replaces total with a bigger value.
start
—
—
0
1
10
0
10
2
8
10
18
3
9
18
27
Common mistakes
Mistake: forgetting to indent the loop body.
for name in names:
print (name)
The print line must be indented so Python knows it belongs inside the loop.
Mistake: resetting the total inside the loop.
scores = [10 , 8 , 9 ]
for score in scores:
total = 0
total = total + score
print (total)
This prints only the last score because total becomes 0 on every trip. Put total = 0 before the loop.
Check your understanding
{
const scripts = Array . from (
document . querySelectorAll ("script.fcpython-ojs-quiz-config" )
);
const script = scripts. find (
(node) => node. dataset . fcpythonRendered !== "true"
);
if (! script) {
return html `<div class="fcpython-quiz fcpython-quiz-warning">
Quiz configuration was not found.
</div>` ;
}
script. dataset . fcpythonRendered = "true" ;
const quiz = JSON . parse (script. textContent );
const container = html `<div class="fcpython-quiz"></div>` ;
const title = document . createElement ("h3" );
title. textContent = quiz. title ;
container. appendChild (title);
const instructions = document . createElement ("p" );
instructions. textContent = quiz. instructions ;
container. appendChild (instructions);
const feedbackNodes = [];
quiz. questions . forEach ((question, questionIndex) => {
const fieldset = document . createElement ("fieldset" );
fieldset. className = "fcpython-quiz-question" ;
const legend = document . createElement ("legend" );
legend. textContent = ` ${ questionIndex + 1 } . ${ question. prompt } ` ;
fieldset. appendChild (legend);
question. options . forEach ((option, optionIndex) => {
const label = document . createElement ("label" );
label. className = "fcpython-quiz-option" ;
const input = document . createElement ("input" );
input. type = "radio" ;
input. name = ` ${ quiz. id } - ${ question. id } ` ;
input. value = String (optionIndex);
const text = document . createElement ("span" );
text. textContent = option;
label. appendChild (input);
label. appendChild (text);
fieldset. appendChild (label);
});
const feedback = document . createElement ("p" );
feedback. className = "fcpython-quiz-feedback" ;
feedback. setAttribute ("aria-live" , "polite" );
feedbackNodes. push (feedback);
fieldset. appendChild (feedback);
container. appendChild (fieldset);
});
const actions = document . createElement ("div" );
actions. className = "fcpython-quiz-actions" ;
const check = document . createElement ("button" );
check. type = "button" ;
check. textContent = "Check answers" ;
const reset = document . createElement ("button" );
reset. type = "button" ;
reset. textContent = "Reset" ;
const score = document . createElement ("p" );
score. className = "fcpython-quiz-score" ;
score. setAttribute ("aria-live" , "polite" );
check. addEventListener ("click" , () => {
let correctCount = 0 ;
quiz. questions . forEach ((question, questionIndex) => {
const selected = container. querySelector (
`input[name=" ${ quiz. id } - ${ question. id } "]:checked`
);
const feedback = feedbackNodes[questionIndex];
if (! selected) {
feedback. textContent = "Choose an answer before checking." ;
feedback. className = "fcpython-quiz-feedback" ;
return ;
}
const selectedIndex = Number (selected. value );
if (selectedIndex === question. answer_index ) {
correctCount += 1 ;
feedback. textContent = `✅ Correct. ${ question. explanation } ` ;
feedback. className = "fcpython-quiz-feedback is-correct" ;
} else {
const answer = question. options [question. answer_index ];
feedback. textContent = `❌ Not yet. Correct answer: ${ answer} . ${ question. explanation } ` ;
feedback. className = "fcpython-quiz-feedback is-incorrect" ;
}
});
score. textContent = `Score: ${ correctCount} / ${ quiz. questions . length } ` ;
});
reset. addEventListener ("click" , () => {
container. querySelectorAll ("input[type='radio']" ). forEach ((input) => {
input. checked = false ;
});
feedbackNodes. forEach ((feedback) => {
feedback. textContent = "" ;
feedback. className = "fcpython-quiz-feedback" ;
});
score. textContent = "" ;
});
actions. appendChild (check);
actions. appendChild (reset);
container. appendChild (actions);
container. appendChild (score);
return container;
}
2. while loops repeat while a condition is true
A while loop is best when you do not know ahead of time how many repetitions you need. It keeps running while a condition remains true.
A safe while loop usually has three parts:
a starting value;
a condition that can eventually become false;
an update inside the loop.
Example 2
count = 3
while count > 0 :
print (count)
count = count - 1
print ("Go!" )
Step-by-step explanation
count = 3
The loop starts with count equal to 3.
while count > 0:
Python asks: “Is count greater than 0?”
If yes, the indented block runs.
print(count)
The current value is displayed.
count = count - 1
This update is the stopping progress. It moves count closer to 0.
When count becomes 0, count > 0 is false and the loop stops.
Python continues after the loop and prints Go!.
Trace table
1
True
3
2
2
True
2
1
3
True
1
0
4
False
loop stops
—
Infinite loops
An infinite loop happens when the condition never becomes false.
count = 3
while count > 0 :
print (count)
This never changes count, so count > 0 stays true forever. In notebooks, you may need to interrupt or stop the cell.
Check your understanding
{
const scripts = Array . from (
document . querySelectorAll ("script.fcpython-ojs-quiz-config" )
);
const script = scripts. find (
(node) => node. dataset . fcpythonRendered !== "true"
);
if (! script) {
return html `<div class="fcpython-quiz fcpython-quiz-warning">
Quiz configuration was not found.
</div>` ;
}
script. dataset . fcpythonRendered = "true" ;
const quiz = JSON . parse (script. textContent );
const container = html `<div class="fcpython-quiz"></div>` ;
const title = document . createElement ("h3" );
title. textContent = quiz. title ;
container. appendChild (title);
const instructions = document . createElement ("p" );
instructions. textContent = quiz. instructions ;
container. appendChild (instructions);
const feedbackNodes = [];
quiz. questions . forEach ((question, questionIndex) => {
const fieldset = document . createElement ("fieldset" );
fieldset. className = "fcpython-quiz-question" ;
const legend = document . createElement ("legend" );
legend. textContent = ` ${ questionIndex + 1 } . ${ question. prompt } ` ;
fieldset. appendChild (legend);
question. options . forEach ((option, optionIndex) => {
const label = document . createElement ("label" );
label. className = "fcpython-quiz-option" ;
const input = document . createElement ("input" );
input. type = "radio" ;
input. name = ` ${ quiz. id } - ${ question. id } ` ;
input. value = String (optionIndex);
const text = document . createElement ("span" );
text. textContent = option;
label. appendChild (input);
label. appendChild (text);
fieldset. appendChild (label);
});
const feedback = document . createElement ("p" );
feedback. className = "fcpython-quiz-feedback" ;
feedback. setAttribute ("aria-live" , "polite" );
feedbackNodes. push (feedback);
fieldset. appendChild (feedback);
container. appendChild (fieldset);
});
const actions = document . createElement ("div" );
actions. className = "fcpython-quiz-actions" ;
const check = document . createElement ("button" );
check. type = "button" ;
check. textContent = "Check answers" ;
const reset = document . createElement ("button" );
reset. type = "button" ;
reset. textContent = "Reset" ;
const score = document . createElement ("p" );
score. className = "fcpython-quiz-score" ;
score. setAttribute ("aria-live" , "polite" );
check. addEventListener ("click" , () => {
let correctCount = 0 ;
quiz. questions . forEach ((question, questionIndex) => {
const selected = container. querySelector (
`input[name=" ${ quiz. id } - ${ question. id } "]:checked`
);
const feedback = feedbackNodes[questionIndex];
if (! selected) {
feedback. textContent = "Choose an answer before checking." ;
feedback. className = "fcpython-quiz-feedback" ;
return ;
}
const selectedIndex = Number (selected. value );
if (selectedIndex === question. answer_index ) {
correctCount += 1 ;
feedback. textContent = `✅ Correct. ${ question. explanation } ` ;
feedback. className = "fcpython-quiz-feedback is-correct" ;
} else {
const answer = question. options [question. answer_index ];
feedback. textContent = `❌ Not yet. Correct answer: ${ answer} . ${ question. explanation } ` ;
feedback. className = "fcpython-quiz-feedback is-incorrect" ;
}
});
score. textContent = `Score: ${ correctCount} / ${ quiz. questions . length } ` ;
});
reset. addEventListener ("click" , () => {
container. querySelectorAll ("input[type='radio']" ). forEach ((input) => {
input. checked = false ;
});
feedbackNodes. forEach ((feedback) => {
feedback. textContent = "" ;
feedback. className = "fcpython-quiz-feedback" ;
});
score. textContent = "" ;
});
actions. appendChild (check);
actions. appendChild (reset);
container. appendChild (actions);
container. appendChild (score);
return container;
}
3. break and continue
Sometimes a loop needs extra control:
break exits the loop immediately;
continue skips the rest of the current trip and starts the next trip.
Use them sparingly. If you use them, explain why.
Example 3: Stop when found
names = ["Ada" , "Grace" , "Katherine" ]
for name in names:
if name == "Grace" :
print ("Found Grace!" )
break
Python checks names in order. When name is "Grace", it prints the message and break stops the loop. "Katherine" is never checked.
Example 4: Skip one item
scores = [10 , - 1 , 8 ]
for score in scores:
if score < 0 :
continue
print (score)
The negative score is skipped. The loop still continues with later items.
Common mistakes
Mistake: using break when you meant continue.
If one bad item should be skipped, use continue. If the whole search should stop, use break.
Check your understanding
{
const scripts = Array . from (
document . querySelectorAll ("script.fcpython-ojs-quiz-config" )
);
const script = scripts. find (
(node) => node. dataset . fcpythonRendered !== "true"
);
if (! script) {
return html `<div class="fcpython-quiz fcpython-quiz-warning">
Quiz configuration was not found.
</div>` ;
}
script. dataset . fcpythonRendered = "true" ;
const quiz = JSON . parse (script. textContent );
const container = html `<div class="fcpython-quiz"></div>` ;
const title = document . createElement ("h3" );
title. textContent = quiz. title ;
container. appendChild (title);
const instructions = document . createElement ("p" );
instructions. textContent = quiz. instructions ;
container. appendChild (instructions);
const feedbackNodes = [];
quiz. questions . forEach ((question, questionIndex) => {
const fieldset = document . createElement ("fieldset" );
fieldset. className = "fcpython-quiz-question" ;
const legend = document . createElement ("legend" );
legend. textContent = ` ${ questionIndex + 1 } . ${ question. prompt } ` ;
fieldset. appendChild (legend);
question. options . forEach ((option, optionIndex) => {
const label = document . createElement ("label" );
label. className = "fcpython-quiz-option" ;
const input = document . createElement ("input" );
input. type = "radio" ;
input. name = ` ${ quiz. id } - ${ question. id } ` ;
input. value = String (optionIndex);
const text = document . createElement ("span" );
text. textContent = option;
label. appendChild (input);
label. appendChild (text);
fieldset. appendChild (label);
});
const feedback = document . createElement ("p" );
feedback. className = "fcpython-quiz-feedback" ;
feedback. setAttribute ("aria-live" , "polite" );
feedbackNodes. push (feedback);
fieldset. appendChild (feedback);
container. appendChild (fieldset);
});
const actions = document . createElement ("div" );
actions. className = "fcpython-quiz-actions" ;
const check = document . createElement ("button" );
check. type = "button" ;
check. textContent = "Check answers" ;
const reset = document . createElement ("button" );
reset. type = "button" ;
reset. textContent = "Reset" ;
const score = document . createElement ("p" );
score. className = "fcpython-quiz-score" ;
score. setAttribute ("aria-live" , "polite" );
check. addEventListener ("click" , () => {
let correctCount = 0 ;
quiz. questions . forEach ((question, questionIndex) => {
const selected = container. querySelector (
`input[name=" ${ quiz. id } - ${ question. id } "]:checked`
);
const feedback = feedbackNodes[questionIndex];
if (! selected) {
feedback. textContent = "Choose an answer before checking." ;
feedback. className = "fcpython-quiz-feedback" ;
return ;
}
const selectedIndex = Number (selected. value );
if (selectedIndex === question. answer_index ) {
correctCount += 1 ;
feedback. textContent = `✅ Correct. ${ question. explanation } ` ;
feedback. className = "fcpython-quiz-feedback is-correct" ;
} else {
const answer = question. options [question. answer_index ];
feedback. textContent = `❌ Not yet. Correct answer: ${ answer} . ${ question. explanation } ` ;
feedback. className = "fcpython-quiz-feedback is-incorrect" ;
}
});
score. textContent = `Score: ${ correctCount} / ${ quiz. questions . length } ` ;
});
reset. addEventListener ("click" , () => {
container. querySelectorAll ("input[type='radio']" ). forEach ((input) => {
input. checked = false ;
});
feedbackNodes. forEach ((feedback) => {
feedback. textContent = "" ;
feedback. className = "fcpython-quiz-feedback" ;
});
score. textContent = "" ;
});
actions. appendChild (check);
actions. appendChild (reset);
container. appendChild (actions);
container. appendChild (score);
return container;
}
4. Tracing loops when they surprise you
When a loop produces an unexpected result, do not guess randomly. Trace it.
Debugging routine
Write down the variables that change.
Make a table with one row per loop trip.
Fill in values before and after the loop body.
Check whether the stopping condition eventually becomes false.
Add temporary print() calls if the table is hard to fill.
Practice example
numbers = [2 , 4 , 6 ]
total = 0
for number in numbers:
total = total + number
print ("number:" , number, "total:" , total)
print ("final:" , total)
Temporary prints are like a flashlight. They help you see what the loop is doing. After you understand the program, you can remove them.
Check your understanding
{
const scripts = Array . from (
document . querySelectorAll ("script.fcpython-ojs-quiz-config" )
);
const script = scripts. find (
(node) => node. dataset . fcpythonRendered !== "true"
);
if (! script) {
return html `<div class="fcpython-quiz fcpython-quiz-warning">
Quiz configuration was not found.
</div>` ;
}
script. dataset . fcpythonRendered = "true" ;
const quiz = JSON . parse (script. textContent );
const container = html `<div class="fcpython-quiz"></div>` ;
const title = document . createElement ("h3" );
title. textContent = quiz. title ;
container. appendChild (title);
const instructions = document . createElement ("p" );
instructions. textContent = quiz. instructions ;
container. appendChild (instructions);
const feedbackNodes = [];
quiz. questions . forEach ((question, questionIndex) => {
const fieldset = document . createElement ("fieldset" );
fieldset. className = "fcpython-quiz-question" ;
const legend = document . createElement ("legend" );
legend. textContent = ` ${ questionIndex + 1 } . ${ question. prompt } ` ;
fieldset. appendChild (legend);
question. options . forEach ((option, optionIndex) => {
const label = document . createElement ("label" );
label. className = "fcpython-quiz-option" ;
const input = document . createElement ("input" );
input. type = "radio" ;
input. name = ` ${ quiz. id } - ${ question. id } ` ;
input. value = String (optionIndex);
const text = document . createElement ("span" );
text. textContent = option;
label. appendChild (input);
label. appendChild (text);
fieldset. appendChild (label);
});
const feedback = document . createElement ("p" );
feedback. className = "fcpython-quiz-feedback" ;
feedback. setAttribute ("aria-live" , "polite" );
feedbackNodes. push (feedback);
fieldset. appendChild (feedback);
container. appendChild (fieldset);
});
const actions = document . createElement ("div" );
actions. className = "fcpython-quiz-actions" ;
const check = document . createElement ("button" );
check. type = "button" ;
check. textContent = "Check answers" ;
const reset = document . createElement ("button" );
reset. type = "button" ;
reset. textContent = "Reset" ;
const score = document . createElement ("p" );
score. className = "fcpython-quiz-score" ;
score. setAttribute ("aria-live" , "polite" );
check. addEventListener ("click" , () => {
let correctCount = 0 ;
quiz. questions . forEach ((question, questionIndex) => {
const selected = container. querySelector (
`input[name=" ${ quiz. id } - ${ question. id } "]:checked`
);
const feedback = feedbackNodes[questionIndex];
if (! selected) {
feedback. textContent = "Choose an answer before checking." ;
feedback. className = "fcpython-quiz-feedback" ;
return ;
}
const selectedIndex = Number (selected. value );
if (selectedIndex === question. answer_index ) {
correctCount += 1 ;
feedback. textContent = `✅ Correct. ${ question. explanation } ` ;
feedback. className = "fcpython-quiz-feedback is-correct" ;
} else {
const answer = question. options [question. answer_index ];
feedback. textContent = `❌ Not yet. Correct answer: ${ answer} . ${ question. explanation } ` ;
feedback. className = "fcpython-quiz-feedback is-incorrect" ;
}
});
score. textContent = `Score: ${ correctCount} / ${ quiz. questions . length } ` ;
});
reset. addEventListener ("click" , () => {
container. querySelectorAll ("input[type='radio']" ). forEach ((input) => {
input. checked = false ;
});
feedbackNodes. forEach ((feedback) => {
feedback. textContent = "" ;
feedback. className = "fcpython-quiz-feedback" ;
});
score. textContent = "" ;
});
actions. appendChild (check);
actions. appendChild (reset);
container. appendChild (actions);
container. appendChild (score);
return container;
}
Key points
A for loop repeats once for each item in a collection.
A while loop repeats while a condition is true.
Indentation defines the loop body.
Values that change inside a loop should be traced carefully.
break exits a loop; continue skips to the next trip.
Trace tables are one of the best beginner debugging tools.
References
Python Tutorial: for, range, break, and continue: https://docs.python.org/3/tutorial/controlflow.html
Python Tutorial: data structures: https://docs.python.org/3/tutorial/datastructures.html
Back to top