AWS Builder Center

Python's for loop

The for loop looks identical every time. for x in something:. But what x turns out to be depends on what something is

These four lines show it:

1
2
3
4
for i in range(1, 4): # range hands out numbers 1, 2, 3
for i in [10, 20, 30]: # list hands out its values 10, 20, 30
for i in {3: "Fizz"}: # dict hands out its keys: 3
for i, j in {3: "Fizz"}.items(): # .items() hands out pairs: i=3, j="Fizz"
Most confusing loop bugs in Python come from not knowing which of these you're dealing with.

A loop just asks for the next item

A for loop knows nothing about lists, ranges, or dictionaries. It takes an object, asks for the next item, and repeats until the object says there are no more. Anything that can answer that question is called an iterable, and each iterable decides for itself what to hand over.

range gives numbers

1
2
for i in range(1, 4):
print(i) # 1, 2, 3
range(1, 4) starts at 1 and stops before 4. That trips up a lot of beginners, but it means range(1, 4) has exactly 3 numbers in it, which is 4 - 1.
A range doesn't store its numbers. It makes each one when the loop asks, so range(1, 1_000_000) uses almost no memory. Use it when you need to do something N times or count from A to B.

A list gives its values

1
2
for i in [10, 20, 30]:
print(i) # 10, 20, 30
You get the items in order. You don't get their positions. If i is 20, nothing about i tells you it sat at index 1. When you need the position too, use enumerate:
1
2
for index, value in enumerate([10, 20, 30]):
print(index, value) # 0 10, then 1 20, then 2 30

A dict gives its keys

1
2
for i in {3: "Fizz"}:
print(i) # 3
This is the one that catches people. You'd reasonably expect pairs, or at least values. You get keys only. A dictionary is a lookup table, and the key is how you look anything up, so the key is what it treats as the entry. To get the value, look it up:
1
2
3
d = {3: "Fizz"}
for k in d:
print(k, d[k]) # 3 Fizz
That works, but it does a second lookup on every pass. There's a better way.

.items() gives pairs

1
2
for i, j in {3: "Fizz"}.items():
print(i, j) # 3 Fizz
.items() changes what the dictionary hands out. Each step is now a tuple like (3, "Fizz"). Python lets you unpack a tuple straight into several variables in the loop header, so for i, j in ... puts the key in i and the value in j.
If you only need values, use .values().
You wantUse
Keysfor k in d
Valuesfor v in d.values()
Bothfor k, v in d.items()

The bug this causes

1
2
3
scores = {"Ana": 90, "Budi": 75}
for s in scores:
print(s + 5) # TypeError: can only concatenate str (not "int") to str
The programmer thought s was a score. It's the key, "Ana", and you can't add 5 to a string. Looping over .values() or .items() fixes it, depending on whether you also need the name.

Name your variables

i and j are fine in a demo. In real code, name the thing:
1
2
for name, score in scores.items():
print(f"{name} scored {score}")
When name and score are right there in the loop header, the bug above is hard to write in the first place.

Summary

Same syntax, different object after in. A range gives numbers, a list gives values, a dict gives keys, and .items() gives key-value pairs. The next time a loop variable isn't what you expected, check what the object after in hands out. That's usually the whole bug.
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article