Lesson Review
Looping Through Dictionaries
for name in grades:.keys().values().items()"Ava" in grades
Visit every key, every value, or every pair, and check whether a key exists before you use it.
By the end of this lesson you will be able to:
- Iterate through a dictionary's keys with
for key in d or d.keys().
- Iterate through its values with
d.values().
- Iterate through its key-value pairs with
d.items().
- Use
in (and not in) to check whether a key exists.
How to use this page: Use Next and Back (or the arrow keys). Watch the Loop Visualizer highlight each pair, test keys in the Key Detective, and build loops in the Loop Builder challenge.
Words you will hear today
| Word | What it means | Example |
| iterate | Visit each item, one at a time, in a loop | for name in grades: |
| view | A live window into a dictionary's keys, values, or pairs | grades.keys() |
| unpacking | Splitting a pair into two variables | for name, grade in ... |
| membership test | Checking whether something is inside with in | "Ava" in grades |
| insertion order | The order the pairs were first added | Ava, Ben, Cy |
- keys, values, items, and in.
- Iterate, view, unpacking, membership test, insertion order.
Last time we built dictionaries and looked up one value at a time. Today we visit every entry: all the keys, all the values, or all the pairs. And we practice the safest habit with dictionaries: checking that a key exists before we use it.
Review
Quick review: one dictionary, three views
grades = {"Ava": 92, "Ben": 85, "Cy": 77}
| Method | Gives a view of the... | With grades |
grades.keys() | keys | dict_keys(['Ava', 'Ben', 'Cy']) |
grades.values() | values | dict_values([92, 85, 77]) |
grades.items() | (key, value) pairs | dict_items([('Ava', 92), ('Ben', 85), ('Cy', 77)]) |
Python describes keys() as returning "a set-like object providing a view on the dict's keys." A view is a live window into the dictionary, not a separate copy.
- All three work in a
for loop.
- All three go in insertion order: the order the pairs were added.
- Wrap a view in
list() when you need a real list: list(grades.keys()).
- A view is not a list, so
grades.keys()[0] is TypeError: 'dict_keys' object is not subscriptable.
In this lesson, every example uses the same gradebook so you can compare the loops side by side.
How to pick the right view
Ask yourself what the loop needs to know on each pass:
- Only the names (keys)? → loop over the dictionary itself, or
.keys().
- Only the numbers (values)? →
.values().
- Both, together? →
.items().
Why "insertion order" matters: if you add Ava, then Ben, then Cy, every loop visits them in that order. Updating Ben's grade does not move him. Only removing a key and adding it back puts it at the end.
- keys(), values(), items() give views.
- Insertion order. list() to make a list.
- Need keys, values, or both? That picks the view.
Every dictionary gives you three windows into it. Keys shows the names, values shows the grades, and items shows the name and grade together as pairs. They are called views because they look into the dictionary rather than copying it. If the dictionary changes, the view changes too.
Keys
Looping over the keys
A plain for loop over a dictionary gives you each key:
for name in grades:
print(name)
Ava
Ben
Cy
Need the value too? Use the key to look it up:
for name in grades:
print(name, "scored", grades[name])
for name in grades: and for name in grades.keys(): do the same thing. Many programmers write .keys() anyway because it makes the code easier to read.
What happens on each pass
- Pass 1:
name is "Ava". grades[name] looks up 92.
- Pass 2:
name is "Ben". grades[name] looks up 85.
- Pass 3:
name is "Cy". grades[name] looks up 77.
The lookup inside the loop is always safe, because every key the loop gives you is guaranteed to be in the dictionary.
Keys are perfect for a roster or a menu: print every name, or every item someone can order, without needing the values at all.
- for key in d: gives keys.
- d[key] inside the loop gives the value.
- d[key] inside a key loop never causes a KeyError.
When you loop over a dictionary directly, Python hands you the keys, one at a time, in the order they were added. Inside the loop, the key is all you need to look up the value with square brackets. Step through and watch the name variable change.
Keys
Working with .keys()
| Code | Result |
grades.keys() | dict_keys(['Ava', 'Ben', 'Cy']) |
list(grades.keys()) | ['Ava', 'Ben', 'Cy'] |
len(grades.keys()) | 3 |
sorted(grades.keys()) | a sorted list of keys |
Views are live. If you save names = grades.keys() and then add a new student, names shows the new key too, without calling .keys() again.
Step through and watch what names shows before and after a new key is added.
What "live" means
A view is not a snapshot; it always shows the dictionary as it is right now. In the demo, names was created before Di was added, yet printing it afterward includes Di. A list made with list(grades.keys()) is different: it is a copy taken at that moment and does not update.
Sorting keys: sorted(grades) gives a new alphabetical list without changing the dictionary's order. To sort names by their grades instead, use sorted(grades, key=grades.get, reverse=True), which gives ['Ava', 'Ben', 'Cy'] (highest grade first).
- keys() view; list(), len(), sorted() work on it.
- Views update automatically.
- A view stays current; list(d.keys()) is a snapshot.
The keys method gives a view of the keys. You can count it, sort it, or turn it into a list. The surprising part is that a view stays connected to the dictionary. Step through: we save the view, add Di, and the saved view now includes Di.
Values
Looping over the values with .values()
for grade in grades.values():
print(grade)
92
85
77
Values are perfect for math on the whole dictionary:
total = sum(grades.values()) # 254
average = total / len(grades) # 84.666...
print(max(grades.values())) # 92
print(min(grades.values())) # 77
Values do not know their keys. Inside for grade in grades.values(): you cannot tell who earned that grade. If you need the name too, use .items() (next slide).
Counting with values
passing = 0
for grade in grades.values():
if grade >= 80:
passing += 1
print(passing, "students scored 80 or higher") # 2
This is the counting pattern you already know, applied to the values. Because you only need the numbers, .values() is the simplest choice.
Check for an empty dictionary before computing an average: len(grades) of 0 would cause a ZeroDivisionError, and max() of no values is a ValueError.
- values() for totals, averages, max, min.
- No way back to the key from a value.
- values() + counting pattern = how many match.
When you only care about the numbers, loop over values. Sum, max, and min all work directly on the values view. But values are only half the story. If you find the highest grade, values cannot tell you whose grade it was.
Key-Value Pairs
Looping over pairs with .items()
The tutorial: "When looping through dictionaries, the key and corresponding value can be retrieved at the same time using the items() method."
for name, grade in grades.items():
print(name, "scored", grade)
Ava scored 92
Ben scored 85
Cy scored 77
- Each item is a tuple:
('Ava', 92).
name, grade unpacks the tuple, just like x, y = point from the tuples lesson.
- Pick names that describe the pair:
for student, grade in ..., for item, price in ....
Forgetting .items() is a common bug: for name, grade in grades: tries to unpack each key (a string), which usually gives ValueError.
What .items() hands the loop
On each pass, Python gets one tuple, like ('Ben', 85), and splits it: name = 'Ben', grade = 85. It is exactly the same as writing name, grade = ('Ben', 85).
A sneaky version of the bug
Without .items(), Python tries to unpack each key string into two names. "Ava" has 3 letters, so it fails. But "Cy" has exactly 2 letters, so it "works": name becomes 'C' and grade becomes 'y'! If you ever see single letters where you expected names and numbers, check for a missing .items().
- for k, v in d.items(): key and value together.
- Each item is a tuple that gets unpacked.
- Missing .items() can split two-letter keys into letters.
Items is the one you will use most. It gives you each key and value together as a pair, and the two names after for unpack the pair. Try the option without items to see the error you get when you forget it.
Practice
Loop Visualizer
Pick a loop and click Next (or Play). The highlighted row is the pair Python is visiting; the variables and output update at each step. Nothing is recorded.
Compare the three loops
| Loop | Pass 1 variable(s) |
for name in grades: | name = 'Ava' |
for g in grades.values(): | g = 92 |
for name, g in grades.items(): | name = 'Ava', g = 92 |
All three visit the pairs in the same order. The only difference is what you get to hold on each pass.
- Compare what the loop variable holds in each style.
Run the same dictionary through all four loops. In the keys loop, the variable holds a name. In the values loop, it holds a number. In the items loop, two variables hold both. Notice that every loop visits the rows in the same top-to-bottom order.
Formatting
Printing a neat report with f-strings
Loops over .items() often print a report. An f-string (a string with f in front) lets you put values right inside the text with { }:
for name, grade in grades.items():
print(f"{name}: {grade}")
Ava: 92
Ben: 85
Cy: 77
| Inside the braces | Meaning | Example result |
{name:<6} | left-align in 6 spaces | 'Ava ' |
{grade:>4} | right-align in 4 spaces | ' 92' |
{avg:.1f} | 1 digit after the decimal point | '84.7' |
Reading a format code
Everything after the colon inside { } is a format instruction:
< means left-align, > right-align, and the number is the total width. Text usually looks best left-aligned and numbers right-aligned, so the digits line up.
.1f means a float with 1 digit after the decimal point. .2f is good for money.
- You can combine them:
{avg:>6.1f} right-aligns in 6 spaces with 1 decimal place.
Quotes inside an f-string: if the f-string uses double quotes, use single quotes for any string inside the braces, like f"{'Name':<6}".
- f"{name}: {grade}" puts values in text.
- :<6, :>4, :.1f for alignment and decimals.
- < left, > right, number = width, .1f = 1 decimal.
Printing pairs is so common that it is worth learning f-strings now. Put an f before the quotes, and anything in curly braces is replaced by its value. After a colon you can add formatting: less-than means left-align, greater-than means right-align, and point one f means one digit after the decimal point.
Key Existence
Does a key exist? Use in
The tutorial: "To check whether a single key is in the dictionary, use the in keyword."
"Ava" in grades # True
"Dee" in grades # False
"Dee" not in grades # True
- The result is a boolean, so it fits right into an
if.
- The Python reference says that for a dictionary, "
in tests whether the dictionary has a given key." It checks keys only, not values: 92 in grades is False.
- It is case sensitive:
"ava" in grades is False.
name = input("Student: ")
if name in grades:
print(name, "has", grades[name])
else:
print("No grade for", name)
Type a name and run. The if keeps the program safe from a KeyError.
Why check before you look up?
A program that uses user input cannot know in advance which names someone will type. One typo would crash the program with a KeyError. The if name in grades: check turns that crash into a friendly message.
Making the check friendlier
Users often type extra spaces or different capitals. Cleaning the input first helps, as long as your keys are stored the same way:
name = input("Student: ").strip().title() # " ava " becomes "Ava"
if name in grades:
print(name, "has", grades[name])
- key in d: True/False. not in: the opposite.
- Checks keys only, case sensitive.
- Clean input with .strip().title() before checking.
Before you use square brackets on a key that might be missing, ask first. The in keyword answers yes or no: is this key in the dictionary? Put it in an if statement, and your program never crashes with a KeyError. Try a name that is there, one that is not, and one with the wrong capitalization.
Key Existence
Checking values and pairs
| Question | Code | Result |
| Is there a key "Ben"? | "Ben" in grades | True |
| Did anyone score 85? | 85 in grades.values() | True |
| Is 85 a key? | 85 in grades | False |
| Did Ben score exactly 85? | ("Ben", 85) in grades.items() | True |
| Is "Dee" missing? | "Dee" not in grades | True |
Speed note: checking a key is what dictionaries are built for. Checking .values() has to look through the values one by one, so it is slower on very large dictionaries.
Finding who had a certain value
85 in grades.values() tells you whether someone scored 85, but not who. To find the name, loop over the pairs:
for name, grade in grades.items():
if grade == 85:
print(name, "scored 85") # Ben scored 85
More than one student might have the same value, so this loop can find several matches. Keys, on the other hand, are always unique.
- in d / in d.keys(): keys. in d.values(): values. in d.items(): exact pairs.
- To find who has a value, loop over .items().
A plain in checks keys. If you want to know whether a value appears anywhere, check the values view. And to check an exact pair, check the items view with a tuple. Run each option and compare.
Practice
Key Detective
Type a key (text needs quotes) and click Investigate. See what in, square brackets, and .get() each do with it. Nothing is recorded.
What each tool does with a missing key
| Tool | Key present | Key missing |
key in d | True | False |
d[key] | the value | KeyError |
d.get(key) | the value | None |
Try 92 as the key to see that values are not keys.
- Try "Ava", "ava", "Dee", and 92.
Use the detective to compare the three ways of handling a key. When the key exists, all three agree. When it is missing, in says False, square brackets crash with a KeyError, and get quietly gives back the default.
Patterns
Common patterns
| Goal | Pattern |
| Find who scored highest | loop .items(), remember the best pair |
| Count matches | loop .values(), add 1 when the if is True |
| Filter into a list | loop .items(), append the keys you want |
| Print in alphabetical order | for name in sorted(grades): |
| Build a new dictionary | loop .items(), store into a new dict |
The tutorial: "To loop over a sequence in sorted order, use the sorted() function which returns a new sorted list while leaving the source unaltered."
Building a new dictionary from an old one
curved = {}
for name, grade in grades.items():
curved[name] = grade + 5
print(curved) # {'Ava': 97, 'Ben': 90, 'Cy': 82}
Python also has a one-line dictionary comprehension for this, just like list comprehensions: {name: grade + 5 for name, grade in grades.items()}. Add an if at the end to filter: {n: g for n, g in grades.items() if g >= 80} keeps only Ava and Ben.
- Highest, count, filter, sorted, new dict.
- Build a new dict in a loop, or with {k: v for k, v in d.items()}.
Most dictionary loops follow a handful of patterns. Step through each one. The "find the highest" pattern is worth memorizing: start with nothing, and every time you see a better grade, remember that name and grade.
Careful
Changing a dictionary while looping
| Inside the loop... | Safe? |
change an existing value: grades[name] += 5 | Yes |
| add a new key | No: RuntimeError |
| delete a key | No: RuntimeError |
The error is RuntimeError: dictionary changed size during iteration.
Fixes: loop over a copy of the keys with for name in list(grades):, or build a new dictionary. The tutorial's advice for lists applies here too: "it is often simpler and safer to create a new list instead."
Why adding keys during a loop is a problem
The loop is walking through the dictionary's keys in order. If the dictionary grows or shrinks partway through, Python can no longer be sure which keys it has visited, so it stops with an error instead of guessing.
The safe way to remove failing students
for name in list(grades): # a snapshot of the keys
if grades[name] < 80:
del grades[name] # safe: we loop over the snapshot
list(grades) makes a separate list of the keys before the loop starts, so deleting from the dictionary does not disturb the loop.
- Changing values: fine. Adding or removing keys: RuntimeError.
- Loop over list(d) or build a new dict.
- Loop over list(d) when deleting keys.
Changing a value during a loop is fine, because the set of keys stays the same. But adding or removing keys while Python is walking through them causes a RuntimeError. The fix is to loop over a list copy of the keys, or to build a brand new dictionary with only what you want.
Practice
Loop Builder challenge
Fill in the blanks with the dropdowns so each program prints exactly the target output, then click Check. The code really runs. Practice only; nothing is recorded.
Strategy
- Look at the target output. Does each line show a key, a value, or both?
- Pick
d/.keys(), .values(), or .items() to match.
- Is the order alphabetical? Then you need
sorted().
- Are only some entries shown? Then you need an
if inside the loop.
- Match the loop style to what the target output needs.
Look at each target output first. Does it show names, grades, or both? That tells you whether you need keys, values, or items. Then make sure the print uses the right variable.
Debugging
Common mistakes
| Mistake | Code | What happens (Python 3.13) | Fix |
Two names without .items() | for k, v in grades: | ValueError: too many values to unpack (expected 2) | for k, v in grades.items(): |
| Expecting values from a plain loop | for g in grades: total += g | TypeError: unsupported operand type(s) for +=: 'int' and 'str' | for g in grades.values(): |
| Indexing a view | grades.keys()[0] | TypeError: 'dict_keys' object is not subscriptable | list(grades)[0] |
Checking a value with plain in | 92 in grades | No error. False, because 92 is not a key | 92 in grades.values() |
| Wrong capitalization | "ava" in grades | No error. False | Match the key exactly |
| Using a key without checking | grades["Dee"] | KeyError: 'Dee' | if "Dee" in grades: first |
| Adding keys inside the loop | for k in grades: grades[k + "2"] = 0 | RuntimeError: dictionary changed size during iteration | for k in list(grades): |
| Forgetting the parentheses | for v in grades.values: | TypeError: 'builtin_function_or_method' object is not iterable | grades.values() |
Try these at the prompt:
g = {"Ava": 92, "Ben": 85}
g.items()
92 in g
92 in g.values()
"Ben" not in g
list(g)
for k, v in g.items():
print(f"{k}: {v}")
Reading the error messages
- "too many values to unpack" or "not enough values": you wrote two loop names but forgot
.items().
- "unsupported operand ... 'int' and 'str'": you are doing math on keys (strings) instead of values.
- "not subscriptable": you used
[0] on a view. Wrap it in list().
- "not iterable": you forgot the
() after .values, .keys, or .items.
- No error, but False: plain
in only checks keys, and checks capitals exactly.
- Most bugs: wrong view, or forgetting .items().
- "not iterable" usually means missing () after .values/.keys/.items.
Most dictionary loop bugs come from using the wrong view. Ask: do I need keys, values, or both? And remember that a plain in only looks at keys. Checking for a value needs the values view.
Practice
Check yourself
Every question uses grades = {"Ava": 92, "Ben": 85, "Cy": 77}. Choose an answer, then click Check. Practice only; nothing is graded or recorded.
For each question, ask:
- Which view is the loop using: keys, values, or items?
- What does the loop variable hold on the first pass?
- For
in: is it checking keys (plain), values, or exact pairs?
- Keys, values, or pairs? Then trace.
For each question, first decide whether the loop or check is looking at keys, values, or pairs. Then trace through the three students in order.
Summary
Cheat sheet
Loop over...
for name in grades: # keys
for name in grades.keys(): # keys
for g in grades.values(): # values
for name, g in grades.items(): # pairs
for name in sorted(grades): # keys, A-Z
Check if it exists
"Ava" in grades # key?
"Dee" not in grades # key missing?
92 in grades.values() # value?
("Ava", 92) in grades.items()
if name in grades:
print(grades[name])
Handy with views
list(grades) # list of keys
len(grades) # how many
sum(grades.values()) # total
max(grades.values()) # highest
print(f"{name}: {g}") # neat output
Remember:
- A plain loop or a plain
in works with keys
- Need both key and value? Use
.items() and two loop names
- Check with
in before using d[key] on a key that might be missing
- Do not add or remove keys while looping; loop over
list(d) instead
Key words to know
keys() · values() · items() · view · insertion order · unpacking · in / not in · f-string · format code · dictionary comprehension · RuntimeError
- keys, values, items, in.
- Know the key words: view, items, unpacking, in, f-string.
Let's wrap up. Looping over a dictionary gives keys by default. Use values for the numbers and items for both. And before you use a key that might not exist, check it with in. These four tools cover almost every dictionary loop you will write.