Iterate Through a Dictionary in Python (keys, values, items)

To iterate through a dictionary in Python and get both parts, loop over .items():

for name, score in scores.items():
    print(name, score)

Looping over the dictionary on its own gives you the keys and nothing else, which is the detail most people trip over first.

Below: the four ways to loop, sorting as you go, nested dictionaries, and the RuntimeError that appears when you delete inside the loop. Output is from Python 3.12.5.

Diagram comparing looping a Python dictionary directly with keys values and items
The same dictionary, four ways to loop over it.

A plain loop gives you the keys

This is where the confusion starts:

scores = {"ann": 90, "bob": 85, "cat": 95}

for name in scores:
    print(name)

print()
print("looping a dictionary directly gives you the KEYS, not the values.")
print("This is the single most common surprise with dictionaries.")
print()
print("to reach a value, look it up:")
for name in scores:
    print(" ", name, "scored", scores[name])

Output:

ann
bob
cat

looping a dictionary directly gives you the KEYS, not the values.
This is the single most common surprise with dictionaries.

to reach a value, look it up:
  ann scored 90
  bob scored 85
  cat scored 95
Command Prompt showing that looping a Python dictionary directly yields only the keys
for name in scores yields keys, so values need a lookup.

for name in scores is shorthand for for name in scores.keys(). Both do the same thing, and the shorter form is the one you will see most often.

Looking the value up afterwards works, but it searches the dictionary a second time for something you already had. Checking whether a key exists covers the lookup itself.

Iterate over keys and values with items()

.items() hands back both halves on every pass:

scores = {"ann": 90, "bob": 85, "cat": 95}

for name, score in scores.items():
    print(name, "scored", score)

print()
print(".items() hands back both parts at once, so there is no lookup.")
print("It is the form to reach for by default.")
print()
print("what the three views actually contain:")
print("  scores.keys()   ->", list(scores.keys()))
print("  scores.values() ->", list(scores.values()))
print("  scores.items()  ->", list(scores.items()))

Output:

ann scored 90
bob scored 85
cat scored 95

.items() hands back both parts at once, so there is no lookup.
It is the form to reach for by default.

what the three views actually contain:
  scores.keys()   -> ['ann', 'bob', 'cat']
  scores.values() -> [90, 85, 95]
  scores.items()  -> [('ann', 90), ('bob', 85), ('cat', 95)]
Command Prompt showing a Python loop over dictionary items unpacking the key and the value
.items() unpacks into two variables, with no lookup.
LoopGives youUse when
for k in d:KeysYou only need the keys
for k in d.keys():KeysYou want the intent spelled out
for v in d.values():ValuesThe keys are irrelevant
for k, v in d.items():BothAlmost always

Pulling one side out on its own is also useful: sum(d.values()) or a list of the keys needs no loop at all.

keys(), values() and items() are live views

They are not copies, which occasionally matters:

scores = {"ann": 90, "bob": 85}

names = scores.keys()
print("names right after taking .keys() ->", list(names))

scores["cat"] = 95
print("after adding cat, the SAME view  ->", list(names))
print()
print("views are windows onto the dictionary, not copies.")
print("They update themselves when the dictionary changes.")
print()
print("their types are not lists:")
print("  type(scores.keys())  ->", type(scores.keys()).__name__)
print("  type(scores.items()) ->", type(scores.items()).__name__)
print()
print("wrap in list() when you need a snapshot that will not move.")

Output:

names right after taking .keys() -> ['ann', 'bob']
after adding cat, the SAME view  -> ['ann', 'bob', 'cat']

views are windows onto the dictionary, not copies.
They update themselves when the dictionary changes.

their types are not lists:
  type(scores.keys())  -> dict_keys
  type(scores.items()) -> dict_items

wrap in list() when you need a snapshot that will not move.
Command Prompt showing that a Python dictionary keys view updates when a new key is added
A view reflects later changes to the dictionary.

A view is a window onto the dictionary. Adding a key afterwards shows up in a view you took earlier, and the view costs almost nothing to create regardless of size.

Wrap it in list() when you want a snapshot, which is exactly the trick used below to delete safely.

Dictionary order and sorting while you loop

Since Python 3.7, dictionaries keep the order things were added:

scores = {}
scores["zoe"] = 70
scores["ann"] = 90
scores["bob"] = 85

print("dictionaries keep insertion order:")
print(" ", list(scores))
print()
print("zoe was added first, so zoe comes first, even though")
print("that is not alphabetical.")
print()

print("sorted by key:")
for name, score in sorted(scores.items()):
    print("  ", name, score)

print()
print("sorted by value, highest first:")
for name, score in sorted(scores.items(), key=lambda pair: pair[1], reverse=True):
    print("  ", name, score)

print()
print("backwards through insertion order:")
print(" ", list(reversed(scores)))

Output:

dictionaries keep insertion order:
  ['zoe', 'ann', 'bob']

zoe was added first, so zoe comes first, even though
that is not alphabetical.

sorted by key:
   ann 90
   bob 85
   zoe 70

sorted by value, highest first:
   ann 90
   bob 85
   zoe 70

backwards through insertion order:
  ['bob', 'ann', 'zoe']
Command Prompt showing a Python dictionary sorted by key and by value during iteration
sorted() on the items sorts by key, or by value with a key function.

sorted(d.items()) orders by key. To order by value, pass key=lambda pair: pair[1], and add reverse=True for largest first.

Neither sorts the dictionary itself; both produce a sorted list of pairs to loop over. reversed(d) walks insertion order backwards.

RuntimeError: dictionary changed size during iteration

Deleting a key inside the loop stops it dead:

scores = {"ann": 90, "bob": 85, "cat": 95}

try:
    for name in scores:
        if name == "bob":
            del scores[name]
except RuntimeError as err:
    print("deleting while looping -> RuntimeError:", err)

print("and the dictionary is left half-processed:", scores)
print()

scores = {"ann": 90, "bob": 85, "cat": 95}
for name in list(scores):
    if name == "bob":
        del scores[name]
print("safe, looping over list(scores) ->", scores)
print()

scores = {"ann": 90, "bob": 85, "cat": 95}
print("safe, building a new dictionary ->",
      {k: v for k, v in scores.items() if k != "bob"})

Output:

deleting while looping -> RuntimeError: dictionary changed size during iteration
and the dictionary is left half-processed: {'ann': 90, 'cat': 95}

safe, looping over list(scores) -> {'ann': 90, 'cat': 95}

safe, building a new dictionary -> {'ann': 90, 'cat': 95}
Command Prompt showing the Python RuntimeError dictionary changed size during iteration
Removing a key mid-loop raises, and leaves the work half done.

Python refuses because the loop is walking the dictionary’s internal layout, and removing an entry can move the others. Rather than silently skip items the way lists do, it raises.

Loop over list(d) to iterate a snapshot while changing the original, or build a new dictionary with a comprehension. The second reads better and is usually what you meant. Checking whether a dictionary is empty is worth pairing with it when everything might be removed.

Changing values while looping is allowed

Only a change in size is a problem:

scores = {"ann": 90, "bob": 85, "cat": 95}

for name in scores:
    scores[name] = scores[name] + 1

print("adding one to every value while looping ->", scores)
print()
print("this is allowed. Only changing the SIZE of the dictionary")
print("raises, and updating a value leaves the size alone.")
print()

print("a comprehension says the same thing in one line:")
scores = {"ann": 90, "bob": 85, "cat": 95}
print(" ", {name: score + 1 for name, score in scores.items()})

Output:

adding one to every value while looping -> {'ann': 91, 'bob': 86, 'cat': 96}

this is allowed. Only changing the SIZE of the dictionary
raises, and updating a value leaves the size alone.

a comprehension says the same thing in one line:
  {'ann': 91, 'bob': 86, 'cat': 96}