How to loop through a dictionary in Python
To loop through a dictionary in Python, use .items() for keys and values together. Plus values(), the order rules, and changing a dict while looping.
To loop through a dictionary in Python and get each key with its value, use .items() and unpack the pair in the loop:
prices = {"apple": 1.2, "bread": 2.5, "milk": 0.9}
for name, price in prices.items():
print(name, price)
apple 1.2
bread 2.5
milk 0.9
.items() gives you each entry as a (key, value) pair, and for name, price in unpacks the pair into two variables. Here are the other forms:
| You want | Write |
|---|---|
| keys only | for key in d: |
| values only | for value in d.values(): |
| keys and values | for key, value in d.items(): |
| keys in sorted order | for key in sorted(d): |
| a counter as well | for i, (key, value) in enumerate(d.items(), start=1): |
The rest of this post covers what order those loops use, the error you get when you change a dictionary mid-loop (and the damage that happens before it), and a mistake that only fails on some data. Every output below was measured in Pyodide running Python 3.13.2.
Looping over keys: the default
Looping over a dictionary directly gives you its keys:
for name in prices:
print(name)
apple
bread
milk
for name in prices.keys(): does exactly the same thing. The .keys() is optional, and most Python code leaves it out.
If you need the value too, you can look it up with prices[name] inside the loop. It works, but .items() is clearer and faster, because it hands over the value it already has instead of finding it again. Looping over a 100,000-entry dictionary ten times took 58.9ms with key-then-lookup, 39.9ms with .items(), and 24.4ms with .values() alone.
values() and items()
When you only need the values, say so:
total = 0
for price in prices.values():
total += price
print(round(total, 2)) # 4.6
(sum(prices.values()) does the same in one call.)
The mistake that only fails on some data
Forgetting .items() is the most common slip, and what happens next depends on your keys:
for name, price in prices:
pass
# ValueError: too many values to unpack (expected 2)
Without .items(), each loop step gets just a key, the string "apple". Python then tries to unpack that string into two variables, letter by letter, and five letters will not fit into two names.
Now try it with two-letter keys:
for k, v in {"ab": 1, "cd": 2}:
print(k, v)
a b
c d
No error at all. Each two-character key unpacks neatly into two variables, and the values are never touched. Country codes, US state abbreviations and two-letter language codes are all real keys that hide this bug until someone adds a three-letter one.
What order does the loop use?
Dictionaries keep insertion order, and that has been a language guarantee since Python 3.7. A loop visits keys in the order they were first added:
print(list({"b": 1, "a": 2, "c": 3})) # ['b', 'a', 'c']
Two details about that order. Assigning a new value to an existing key keeps its position. Deleting a key and adding it back moves it to the end:
d = {"b": 1, "a": 2}
d["b"] = 9
print(list(d)) # ['b', 'a']
del d["b"]
d["b"] = 1
print(list(d)) # ['a', 'b']
For any other order, sort as you loop. sorted(d) gives the keys alphabetically. To sort by value, sort the items with a lambda that picks the value out:
for name, price in sorted(prices.items(), key=lambda kv: kv[1]):
print(name, price)
milk 0.9
apple 1.2
bread 2.5
Add reverse=True for highest first. The sorting guide covers the other key= options. reversed(prices) also works on dictionaries, since Python 3.8, and gives the keys newest first.
Changing a dictionary while you loop over it
Changing values during the loop is fine:
for name in prices:
prices[name] = round(prices[name] * 2, 2)
print(prices) # {'apple': 2.4, 'bread': 5.0, 'milk': 1.8}
Adding or removing keys is not:
prices = {"apple": 1.2, "bread": 2.5, "milk": 0.9}
for name in prices:
if name == "bread":
del prices[name]
# RuntimeError: dictionary changed size during iteration
Here is the part most guides leave out. The error is raised on the step after the change, so the deletion has already happened by the time you see it. Measured, after catching that RuntimeError, prices no longer contained "bread". If this runs inside a try block that logs the error and carries on, you are left with a half-modified dictionary and a log line that suggests nothing changed.
There are two clean ways to do it. Loop over a copy of the keys, so the dictionary itself is free to change:
prices = {"apple": 1.2, "bread": 2.5, "milk": 0.9}
for name in list(prices):
if prices[name] < 1:
del prices[name]
print(prices) # {'apple': 1.2, 'bread': 2.5}
Or build a new dictionary with only the entries you want, which is usually clearer:
prices = {"apple": 1.2, "bread": 2.5, "milk": 0.9}
cheap_removed = {name: p for name, p in prices.items() if p >= 1}
print(cheap_removed) # {'apple': 1.2, 'bread': 2.5}
Lists have the same trap with different symptoms, covered in the for loop guide.
keys(), values() and items() are live views
The objects these methods return are not copies. They are views that track the dictionary as it changes:
stock = {"apple": 1.2, "bread": 2.5, "milk": 0.9}
keys = stock.keys()
stock["eggs"] = 3.1
print(list(keys)) # ['apple', 'bread', 'milk', 'eggs']
keys was created before "eggs" was added and still includes it. That is why looping over stock.keys() gives the same error as looping over stock when you change its size: it is the same dictionary underneath. list(stock) is a real snapshot.
Key views also support set operations, which makes comparing two dictionaries easy:
a = {"x": 1, "y": 2}
b = {"y": 2, "z": 3}
print(a.keys() & b.keys()) # {'y'}
print(a.keys() - b.keys()) # {'x'}
Counters and nested dictionaries
Wrap .items() in enumerate() for a running number. The brackets around (name, price) are required, because each step gives you the counter and a pair:
for i, (name, price) in enumerate(prices.items(), start=1):
print(i, name, price)
1 apple 1.2
2 bread 2.5
3 milk 0.9
Looping with an index explains enumerate in full.
For dictionaries of dictionaries, unpack the outer level and index into the inner one:
users = {
"ada": {"age": 36, "langs": ["python"]},
"linus": {"age": 28, "langs": ["c", "git"]},
}
for name, info in users.items():
print(name, info["age"], ", ".join(info["langs"]))
ada 36 python
linus 28 c, git
Counting things with a dictionary loop
The most common job for a dictionary loop is counting. Loop over the items, and for each one add 1 to its total, starting from 0 the first time a key is seen:
words = "the cat and the hat and the bat".split()
counts = {}
for w in words:
counts[w] = counts.get(w, 0) + 1
print(counts) # {'the': 3, 'cat': 1, 'and': 2, 'hat': 1, 'bat': 1}
counts.get(w, 0) returns the current count, or 0 if the word has not been seen yet. Writing counts[w] += 1 instead raises a KeyError on the first occurrence of every word, because there is nothing to add 1 to.
The standard library has this built in. collections.Counter does the counting and adds a ranking:
from collections import Counter
c = Counter(words)
print(c.most_common(2)) # [('the', 3), ('and', 2)]
print(c["dog"]) # 0
A Counter is a dictionary underneath, so every loop in this post works on it. Asking for a missing key gives 0 rather than an error.
Dictionaries of lists
When each value is a list, loop over the dictionary and then over each list inside it:
teams = {"red": ["Ada", "Linus"], "blue": ["Grace"]}
for team, members in teams.items():
for name in members:
print(team, name)
red Ada
red Linus
blue Grace
To summarise each list instead of visiting every item, a comprehension over .items() does it in one line:
print({team: len(members) for team, members in teams.items()}) # {'red': 2, 'blue': 1}
Find a key by its value
Dictionaries are built to look up values by key, not the other way round. To find the key for a value, you have to loop:
capitals = {"France": "Paris", "Japan": "Tokyo", "Peru": "Lima"}
print(next((k for k, v in capitals.items() if v == "Tokyo"), None)) # Japan
print(next((k for k, v in capitals.items() if v == "Rome"), None)) # None
next() takes the first match from the generator, and the None at the end is what it returns when nothing matches. Without that default, a missing value raises StopIteration.
This search checks every entry until it finds a match, so it slows down as the dictionary grows. If you do it often, build the reversed dictionary once, as the next section shows, and look values up in that instead.
Looping over two dictionaries
To work with the keys two dictionaries share, loop over the intersection of their key views:
a = {"x": 1, "y": 2}
b = {"y": 20, "z": 3}
for key in a.keys() & b.keys():
print(key, a[key], b[key]) # y 2 20
To combine them instead, Python 3.9 added |: a | b gives {'x': 1, 'y': 20, 'z': 3}, with the right-hand dictionary winning on shared keys. Avoid zip(a.items(), b.items()) for this. It pairs entries by position, not by key, so it only lines up if both dictionaries happen to have the same keys in the same order.
Building a dictionary from a loop
A dictionary comprehension loops and builds in one expression. It is how you filter, transform or invert a dictionary:
inverted = {v: k for k, v in {"a": 1, "b": 1}.items()}
print(inverted) # {1: 'b'}
Inverting has a catch, and the output shows it. Two keys shared the value 1, so after inverting, both wanted to become the key 1. The later one won and "a" was silently lost. Only invert when you know the values are unique.
Quick reference
for k in d: # keys
for v in d.values(): # values
for k, v in d.items(): # both
for k in sorted(d): # alphabetical keys
for k, v in sorted(d.items(), key=lambda kv: kv[1]): # by value
for i, (k, v) in enumerate(d.items(), start=1): # with a counter
for k in list(d): # safe to add or delete keys
{k: v for k, v in d.items() if v} # filter into a new dict
Want to practise dictionaries in lessons where the Python runs in your browser? Start the Python track.
More from the blog
What if __name__ == '__main__' actually does in Python
In Python, if __name__ == '__main__' runs code only when you run the file directly, not when it is imported. What __name__ holds and when you need it.
Read moreHow to reverse a list in Python (and a string)
To reverse a list in Python, use reverse() to change it in place, [::-1] for a reversed copy, or reversed() to loop backwards. Which to use and why.
Read moreReady to write some code?
Put this into practice - start your first free lesson. No setup, no credit card.