Python lesson 5 of 9
Python For Loops and Iteration
Learn Python for loops from the ground up - iterate over lists and strings, use range, enumerate and zip, and steer a loop with break and continue.
Published · Every example on this page was run before it was published.
A list can hold a hundred scores, and a string can hold a thousand characters, but so far the only way you have to look at them is one line of code per value. That does not scale, and it is not how programs are written. What you need is a way to say "do this same thing once for every value in here" — and that is exactly what a loop does. Loops are the point where your programs stop being lists of instructions and start being able to handle data of any size.
What a For Loop Is
A for loop takes a collection of values and runs the same block of code once for each of them, handing you one value at a time.
Think about handing out exam papers in a classroom. You do not write separate instructions for each student — you walk down the row and repeat one action, "give this person their paper", until the row runs out. You do not need to know in advance how many students are seated. The row itself tells you when to stop, because eventually there is nobody left.
A Python for loop works the same way. You point it at a collection, give the current value a name, and write the action once:
chores = ["water plants", "wash dishes", "take out trash"]
for chore in chores:
print("Today I need to:", chore)Today I need to: water plants
Today I need to: wash dishes
Today I need to: take out trashRead that top line as a sentence: for each chore in chores. Three pieces are doing the work.
chores is the collection being walked through. chore is the loop variable — a name you invent,
which Python fills in with a different value on each pass. The colon ends the line, and the indented
block beneath it is the body, the code that repeats. One trip through the body is called an
iteration, so this loop runs three iterations.
The loop variable is an ordinary variable. You choose the name, and a clear singular name (chore for
a list of chores) makes the body read like English. Anything a collection can be walked through
value by value is called iterable, and lists, strings, and ranges are all iterable.
Loops shine when you need to build up an answer across many values. A variable that collects a result as the loop runs is called an accumulator:
prices = [4.50, 2.25, 10.00]
total = 0
for price in prices:
total = total + price
print("Items:", len(prices))
print("Total:", total)Items: 3
Total: 16.75Notice that total is created before the loop. If it were created inside the body it would be reset
to zero on every iteration and the final answer would just be the last price. The accumulator lives
outside the loop; only the updating happens inside it.
Looping Over a String
A string is iterable too, and looping over one hands you a single character at a time, left to right:
word = "loop"
for letter in word:
print(letter)l
o
o
pThat is the foundation of nearly every text-processing task: counting, searching, cleaning, or
translating character by character. Here is a counter that combines a loop with an if:
name = "Priyanka"
vowel_count = 0
for character in name:
if character.lower() in "aeiou":
vowel_count += 1
print(name, "has", vowel_count, "vowels")Priyanka has 3 vowelsTwo things are worth pausing on. character.lower() converts the character to lowercase so that a
capital letter is still recognised — without it, the P and a capital A would be missed. And
in "aeiou" asks whether the character appears anywhere in that string of vowels, which is far shorter
than writing five separate comparisons. The += shorthand means "add to what is already there", so
vowel_count += 1 is another way of writing vowel_count = vowel_count + 1.
Counting with range()
Sometimes you do not have a collection to walk through — you just want to repeat something a fixed
number of times, or count through a sequence of numbers. range() produces exactly that: a stream of
whole numbers, generated one at a time.
With a single argument, range(stop) counts from 0 up to but not including stop:
for number in range(5):
print(number)0
1
2
3
4That is five numbers, starting at 0 and ending at 4. The rule that the stop value is left out is the
single most important thing to remember about range(), and it trips up almost every beginner at
least once. It exists for a good reason: because Python indexes lists starting at 0, range(len(items))
produces precisely the valid index positions of a list with no arithmetic on your part.
With two arguments you choose where the counting starts. range(start, stop) begins at start and
still stops just before stop:
for number in range(1, 6):
print("Count:", number)Count: 1
Count: 2
Count: 3
Count: 4
Count: 5To count 1 through 5, you must write 6 as the stop value. A handy way to think about it: the number
of iterations is stop - start, so range(1, 6) runs 6 - 1, or 5 times.
A third argument sets the step — how much to move each time. A step of 2 counts every other number, and a negative step counts downward:
for even in range(0, 10, 2):
print("Even:", even)
for seconds in range(3, 0, -1):
print("T-minus", seconds)Even: 0
Even: 2
Even: 4
Even: 6
Even: 8
T-minus 3
T-minus 2
T-minus 1Wrapping range() in list() is the fastest way to see exactly what it will produce, which is a good
habit whenever you are unsure about the boundaries:
print(list(range(5)))
print(list(range(2, 7)))
print(list(range(10, 0, -3)))[0, 1, 2, 3, 4]
[2, 3, 4, 5, 6]
[10, 7, 4, 1]The last one steps down by 3 from 10 and stops before reaching 0, so 1 is the final value included.
When you only want to repeat an action and do not care about the number itself, the conventional loop
variable name is a single underscore. It is a normal variable, but Python programmers read _ as
"I am deliberately ignoring this value":
for _ in range(3):
print("beep")beep
beep
beepenumerate() and zip()
Two built-in functions cover the situations a plain for loop handles awkwardly.
enumerate() is for when you need both the position and the value. It wraps the collection and
hands back a pair on each iteration, which you unpack into two loop variables separated by a comma:
colors = ["red", "green", "blue"]
for index, color in enumerate(colors):
print(index, color)0 red
1 green
2 blueCounting from 0 is usually what you want for indexing, but if the number is for a human to read, the
start argument shifts it:
podium = ["Ana", "Ben", "Chi"]
for position, name in enumerate(podium, start=1):
print(position, name)1 Ana
2 Ben
3 ChiImportant: start changes only the number you are given, never which values are visited. The loop
still walks the whole list.
zip() is for parallel iteration — walking two or more collections side by side, taking one
value from each on every pass. It is the right tool when related data lives in separate lists:
students = ["Ana", "Ben", "Chi"]
scores = [91, 78, 84]
for student, score in zip(students, scores):
print(student, "scored", score)Ana scored 91
Ben scored 78
Chi scored 84If the collections are different lengths, zip() stops as soon as the shortest one runs out, silently
ignoring the leftovers. That is worth knowing before it surprises you:
letters = ["a", "b", "c"]
numbers = [1, 2]
print(list(zip(letters, numbers)))[('a', 1), ('b', 2)]Each pair is printed inside round brackets because zip() produces tuples — fixed groupings of
values. Unpacking them into two loop variables, as in the earlier example, is the normal way to use
them.
Steering a Loop: break, continue, and else
By default a loop visits every value. Two keywords change that.
break stops the loop immediately and jumps to the code after it. Use it when you have found what
you were looking for and there is no reason to keep checking:
numbers = [12, 7, 40, 3, 25]
for number in numbers:
print("Checking", number)
if number > 30:
print("Found one over 30:", number)
breakChecking 12
Checking 7
Checking 40
Found one over 30: 40The values 3 and 25 are never examined, because break ended the loop at 40.
continue skips the rest of the current iteration and moves straight to the next value. The loop
keeps going — only this one pass is cut short. It is the natural way to filter out values you want to
ignore:
readings = [3, -1, 8, -4, 5]
total = 0
for reading in readings:
if reading < 0:
continue
total += reading
print("Total of valid readings:", total)Total of valid readings: 16Python also allows an else block attached to a loop, which confuses almost everyone the first
time, because it has nothing to do with the else that follows an if. A loop's else runs when the
loop finished normally — that is, it ran out of values without ever hitting break. Read it as
"if no break happened". It is useful for search problems where you need to report failure:
passwords = ["hunter2", "letmein", "qwerty"]
target = "correcthorse"
for password in passwords:
if password == target:
print("Found it")
break
else:
print("Never found", target)Never found correcthorseChange target to "letmein" and the break fires, so the else is skipped entirely and only
Found it is printed. Without this feature you would need a separate flag variable set to True when
the match is found, then checked after the loop — for/else replaces that bookkeeping.
For Loops Versus While Loops
Python has a second kind of loop. A while loop repeats as long as a condition stays true, and it checks that condition before every pass:
countdown = 3
while countdown > 0:
print(countdown)
countdown -= 1
print("Liftoff")3
2
1
LiftoffThe choice between the two comes down to one question: do you know in advance what you are stepping through? If you are walking a list, a string, or a fixed count of numbers, use a for loop — it cannot forget to advance, so it cannot run forever. Use a while loop when the stopping point depends on something computed as you go, and the number of repetitions is not known up front:
balance = 100
days = 0
while balance > 10:
balance = balance / 2
days += 1
print("Days:", days)
print("Balance:", balance)Days: 4
Balance: 6.25Nothing here says "repeat 4 times" — the loop discovers that number by halving until the condition
fails. The danger of a while loop is the infinite loop: if you forget the line that moves toward
the condition becoming false (here, changing balance), the program never stops. A for loop is immune
to that mistake, which is why it should be your default.
A Worked Example
This small program builds a class report from two parallel lists, using zip(), enumerate(),
continue, break, and a loop else together:
students = ["Ana", "Ben", "Chi", "Dev", "Eli"]
scores = [88, 45, 92, 67, 100]
passing = []
total = 0
for rank, (name, score) in enumerate(zip(students, scores), start=1):
if score < 60:
print(rank, name, "needs a retake")
continue
passing.append(name)
total += score
print(rank, name, "scored", score)
print("Passing:", passing)
print("Average of passing scores:", total / len(passing))
for name, score in zip(students, scores):
if score == 100:
print("Perfect score by", name)
break
else:
print("Nobody scored 100")1 Ana scored 88
2 Ben needs a retake
3 Chi scored 92
4 Dev scored 67
5 Eli scored 100
Passing: ['Ana', 'Chi', 'Dev', 'Eli']
Average of passing scores: 86.75
Perfect score by EliHere is what each part does. The two lists hold related data in matching positions: scores[0] is
Ana's score because students[0] is Ana. passing starts as an empty list and total starts at 0 —
both are accumulators, created before the loop so they survive across iterations.
The loop header does two jobs at once. zip(students, scores) pairs each name with its score, and
enumerate(..., start=1) numbers those pairs beginning at 1. The pattern for rank, (name, score) in
unpacks the result in two stages: rank takes the number, and the brackets around name, score split
the pair that zip() produced. The brackets matter — they tell Python that those two names together
correspond to one item.
Inside the body, any score below 60 prints a retake message and then hits continue, which jumps to
the next student without touching the accumulators. That is why Ben never appears in passing and his
45 never lands in total. Every other student is appended to passing, added into total, and
printed.
After the loop, passing holds the four names that survived the filter, and total / len(passing)
divides the accumulated score by how many names were kept, giving 347 divided by 4.
The second loop is a search. It walks the pairs again looking for a perfect score, and break ends it
the moment Eli is found, so no further students are checked. The else attached to that loop would
print the "Nobody scored 100" line only if the loop had finished without breaking. Since break ran,
it is skipped.
Common Mistakes
Off-by-one from misreading range()
for number in range(1, 5):
print(number)1
2
3
4The intention was to print 1 through 5, but 5 never appears. The stop value in range() is exclusive —
counting halts just before it. The fix is range(1, 6). Whenever a loop is one iteration short or one
too long, this is the first thing to check: for the numbers 1 through n, write range(1, n + 1).
Modifying a list while looping over it
numbers = [1, 2, 2, 3, 2]
for number in numbers:
if number == 2:
numbers.remove(number)
print(numbers)[1, 3, 2]Every 2 was supposed to disappear, and one survived. The reason is that the loop tracks its position by number, but removing an item shifts everything after it one place to the left — so the value that slid into the vacated slot gets skipped entirely. No error is raised, which makes this bug especially nasty. The fix is to never change a list you are iterating over. Build a new one instead:
numbers = [1, 2, 2, 3, 2]
kept = []
for number in numbers:
if number != 2:
kept.append(number)
print(kept)[1, 3]Reaching for range(len(x)) out of habit
tools = ["hammer", "saw", "drill"]
for i in range(len(tools)):
print(tools[i])hammer
saw
drillThis runs correctly, but it takes the long way round: it counts index numbers, then uses each one to
look a value back up. When you only need the values, iterate over the collection directly. When you
genuinely need the position too, use enumerate() — both versions below print the same three lines as
above, with less to get wrong:
tools = ["hammer", "saw", "drill"]
for tool in tools:
print(tool)
for index, tool in enumerate(tools):
print(index, tool)hammer
saw
drill
0 hammer
1 saw
2 drillrange(len(...)) is the right choice in one situation: when you need to assign back into the list by
position, and even then enumerate() usually reads better.
Expecting the loop variable to change the list
prices = [10, 20, 30]
for price in prices:
price = price * 2
print(prices)[10, 20, 30]The list is untouched. The loop variable price is a fresh name holding a copy of each value;
reassigning it rebinds that name and has no effect on the list itself. On the next iteration, Python
overwrites it with the next value anyway. To change the list, write into a position:
prices = [10, 20, 30]
for index, price in enumerate(prices):
prices[index] = price * 2
print(prices)[20, 40, 60]Next Steps
Try the linked practice problem, two-number-sum. It hands you a list of numbers and a target, and
asks you to find the pair that adds up to it — which is a loop inside a loop, with enumerate()
keeping the positions straight so you never pair a number with itself, and break (or an early
return) stopping the search the moment you have an answer. Every idea on this page shows up in it.
Before you start, spend a few minutes in the Python playground. Print
list(range(...)) with a handful of different arguments until the exclusive stop value feels obvious
rather than surprising, and try zip() on two lists of unequal length so the truncation is something
you have seen for yourself.
Write it yourself
Reading about code and writing it are different skills. These exercises practise exactly what this lesson covered; they run in this tab and need no account.
- CountdownA launch sequence that counts itself down from five, then prints a liftoff banner and a small rocket.
- The Clapping GameA count from one to twenty where every third number is replaced by a clap, so the beat is visible straight down the screen.
- Rainfall WeekA seven-day rainfall chart with a row for every day, the week added up underneath it, and the wettest of the seven days picked out.
When you want a harder one
Interview-style problems graded against hidden tests — a big step up from the exercises. Come back to these when the ideas in this lesson feel comfortable rather than new.