Iterating dictionaries in Python has one surprise that catches everybody the first time: for key in my_dict gives you keys, not pairs. Once that is clear, keys(), values() and items() become three obvious tools rather than three things to memorise.
What does for key in my_dict actually iterate?
It iterates the keys. The dictionary itself is not a sequence of key-value pairs, so Python walks its keys, in insertion order.
scores = {"ada": 91, "grace": 88, "alan": 95}
for name in scores:
print(name)
for name in scores:
print(name, "->", scores[name])
The second loop works, but it looks up the value with a second dictionary access each time. That is a wasted lookup whenever the value is going to be used, and it is the reason items() exists.
One related thing to know: a dictionary is not a sequence, so there is no d[0]. Attempting it raises KeyError: 0, because 0 is a perfectly valid key that simply is not present. If you want to reach an entry by position, convert the keys to a list first and index that.
What do keys(), values() and items() return?
Three different views over the same dictionary, each showing a different part of it.
scores = {"ada": 91, "grace": 88, "alan": 95}
print(list(scores.keys()))
print(list(scores.values()))
print(list(scores.items()))
They are called views because they are not lists. A view is a live window onto the dictionary: it holds a reference to the dictionary rather than a snapshot of its contents. The list(...) calls above are there to make the output readable.
That distinction matters in one practical way. If you write keys = d.keys() and then add an entry to d, keys is not stale — it now includes the new key. Code that assumes a snapshot will behave differently from code that assumes a window, so decide which one you mean.
How do you iterate key-value pairs together?
Use items() and unpack each pair. The key and the value arrive as two separate names, which is exactly what you want for readable code.
scores = {"ada": 91, "grace": 88, "alan": 95}
for name, score in scores.items():
print(name, "->", score)
print({name: score + 5 for name, score in scores.items()})
Without unpacking, items() yields tuples, and you would index into each one. Writing for pair in scores.items(): and then pair[0] and pair[1] works, but it throws away the one thing that makes Python readable: meaningful names.
When you only need the values, values() avoids the key lookup entirely and is the cheapest of the three. When you only need the keys, keys() is the same as iterating the dictionary directly.
scores = {"ada": 91, "grace": 88, "alan": 95}
for score in scores.values():
print(score)
print(sum(scores.values()))
print(max(scores.values()))
Choosing between them is a question of what your loop body needs. If the body mentions the key, use items() and unpack it. If the body only cares about the value, use values(). Writing for k in d: and then looking the value up inside the loop is the version to avoid, because it does the lookup twice the work.
What are dict views and why do they matter?
Two things follow from a view being a live window rather than a copy. First, a view reflects changes made to the dictionary after you created it.
scores = {"ada": 91}
view = scores.keys()
scores["grace"] = 88
print(list(view))
Second, the key view behaves like a set, which makes set operations available directly on your dictionary. This is a genuinely useful trick for comparing two mappings.
current = {"ada", "grace", "alan"}
previous = {"grace", "alan", "linus"}
print(current - previous)
print(current & previous)
print(current ^ previous)
print(current | previous)
On a real dictionary, keys() supports the same operations, so you can find the keys that were added, removed or kept between two versions of a configuration without converting either to a set first.
old = {"host": "localhost", "port": 5432, "debug": True}
new = {"host": "db.internal", "port": 5432, "timeout": 30}
print(old.keys() - new.keys())
print(new.keys() - old.keys())
print(old.keys() & new.keys())
Those three lines tell you that debug was dropped, timeout was added, and host and port survived. Diffing two dictionaries with set operations on their keys is a trick worth remembering.
Views answer membership questions too. "port" in d.keys() is the same test as "port" in d, and 5432 in d.values() asks whether a value appears anywhere. The values view is the more expensive of the two, because checking a key can be done by hashing it while checking a value means looking through them all.
How do you build one dictionary from another?
The same unpacking that powers the loop powers a comprehension, which is the compact way to filter, transform or reshape a mapping.
scores = {"ada": 91, "grace": 88, "alan": 95, "linus": 40}
passed = {name: score for name, score in scores.items() if score >= 50}
print(passed)
doubled = {name: score * 2 for name, score in scores.items()}
print(doubled)
inverted = {score: name for name, score in scores.items()}
print(inverted)
The inverted version deserves a warning. If two names share a score, the later one overwrites the earlier one, because a dictionary can only hold one value per key. Swapping keys and values is only safe when the values are unique — which is exactly the case when you are reversing a mapping like a name-to-id table.
Merging two dictionaries is a third common transformation, and the double-star unpacking from the functions module does it cleanly.
defaults = {"host": "localhost", "port": 5432, "debug": True}
overrides = {"port": 5433, "debug": False}
settings = {**defaults, **overrides}
print(settings)
And when the keys and the values arrive in two separate lists, zip() pairs them up and dict() turns the pairs into a mapping. Zip is for the moment of assembly; once you have a dictionary, items() is the better way to walk it again.
names = ["ada", "grace", "alan"]
marks = [91, 88, 95]
report = dict(zip(names, marks))
print(report)
What happens if you change a dict while iterating it?
Python detects it and raises RuntimeError: dictionary changed size during iteration. The check exists because the iterator holds a position in the dictionary, and adding or removing an entry would make that position meaningless.
scores = {"ada": 91}
try:
for name in scores:
scores["extra"] = 0
except RuntimeError as exc:
print("RuntimeError:", exc)
Overwriting the value of an existing key is fine, because the size does not change. Adding or removing keys is not. The safe pattern is to build a new dictionary and use it to replace the old one, or to iterate over a snapshot of the keys.
scores = {"ada": 91, "grace": 88}
updated = {name: score + 5 for name, score in scores.items()}
print(updated)
print("original:", scores)
One related detail: since Python 3.8, reversed() works on a dictionary and on its items, giving you the entries in reverse insertion order. That is handy for building a stack-like history from an ordered mapping.
scores = {"ada": 91, "grace": 88, "alan": 95}
print(list(reversed(scores)))
Iteration patterns
| You want | Write |
|---|---|
| Just the keys | for k in d: |
| Just the values | for v in d.values(): |
| Keys and values | for k, v in d.items(): |
| Keys as a set | set(d.keys()) |
| Compare two dicts' keys | d1.keys() - d2.keys() |
| Build a new dict | {k: v for k, v in d.items()} |
| Reverse insertion order | reversed(d) |
Try it yourself: build a dictionary of five students and their marks. Loop over it once printing only the names, once printing only the marks, and once printing both with the mark graded as a letter. Then use keys() set arithmetic to work out which students are new compared with a second dictionary, and finish by building an upgraded copy with a comprehension while confirming the original is untouched.