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}

Updating a value replaces what a key points at without touching the layout, so the loop carries on safely. Adding or deleting a key is what raises.

A dictionary comprehension expresses the same thing without mutating anything, which is the safer habit. Adding items to a dictionary covers the growing case.

Getting a counter with enumerate

Numbering the entries needs one extra pair of brackets:

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

print("numbering the entries:")
for i, (name, score) in enumerate(scores.items(), start=1):
    print(f"  {i}. {name} scored {score}")

print()
print("the inner brackets matter. enumerate produces pairs like this:")
print(" ", list(enumerate(scores.items()))[:2])
print()
print("so the loop is unpacking a number and a tuple, and the tuple")
print("needs its own set of brackets to split apart.")

Output:

numbering the entries:
  1. ann scored 90
  2. bob scored 85
  3. cat scored 95

the inner brackets matter. enumerate produces pairs like this:
  [(0, ('ann', 90)), (1, ('bob', 85))]

so the loop is unpacking a number and a tuple, and the tuple
needs its own set of brackets to split apart.

enumerate(d.items()) produces a number alongside a (key, value) tuple, so the loop unpacks a number and then the pair. Without the inner brackets Python cannot tell how many values it is splitting.

start=1 is worth adding whenever the number is shown to a person. Looping with an index goes through enumerate in detail.

Iterating nested dictionaries

One loop for each level:

students = {
    "ann": {"maths": 90, "art": 70},
    "bob": {"maths": 60},
}

for name, subjects in students.items():
    print(name)
    for subject, score in subjects.items():
        print("   ", subject, score)

print()
print("one loop per level. The outer gives a name and an inner")
print("dictionary; the inner gives a subject and a score.")
print()

print("a dictionary of lists works the same way:")
groups = {"fruit": ["apple", "pear"], "veg": ["leek"]}
for label, members in groups.items():
    print("  ", label, "->", ", ".join(members))

Output:

ann
    maths 90
    art 70
bob
    maths 60

one loop per level. The outer gives a name and an inner
dictionary; the inner gives a subject and a score.

a dictionary of lists works the same way:
   fruit -> apple, pear
   veg -> leek

The outer loop hands you a key and an inner dictionary; the inner loop then treats that exactly like any other dictionary. A dictionary of lists works the same way, with the inner loop over a list instead.

Depth beyond two or three levels usually reads better as a small recursive function than as stacked loops.

Dictionary comprehensions instead of loops

When the loop exists only to build something, a comprehension is shorter:

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

print("filtering:")
print(" ", {name: score for name, score in scores.items() if score >= 90})
print()
print("transforming the values:")
print(" ", {name: score / 100 for name, score in scores.items()})
print()
print("swapping keys and values:")
print(" ", {score: name for name, score in scores.items()})
print()
print("just the parts you want as a list:")
print(" ", [f"{name}={score}" for name, score in scores.items()])
print()
print("a comprehension replaces a loop that only builds something.")

Output:

filtering:
  {'ann': 90, 'cat': 95}

transforming the values:
  {'ann': 0.9, 'bob': 0.85, 'cat': 0.95}

swapping keys and values:
  {90: 'ann', 85: 'bob', 95: 'cat'}

just the parts you want as a list:
  ['ann=90', 'bob=85', 'cat=95']

a comprehension replaces a loop that only builds something.

Filtering, transforming values and swapping keys with values are all one-liners. Swapping only works when the values are unique and hashable, since they become keys.

Converting a dictionary to a list covers the cases where you want a list out rather than a dictionary.

Python iterate dictionary quick reference

  • for k, v in d.items(): is the default. Use it unless you have a reason not to.
  • A plain for k in d: gives keys only.
  • .keys(), .values() and .items() are live views, not lists.
  • Dictionaries keep insertion order; sorted(d.items()) gives a sorted copy.
  • Deleting inside the loop raises RuntimeError; loop over list(d) instead.
  • Changing values inside the loop is fine, because the size does not change.
  • enumerate(d.items(), start=1) adds a counter, unpacked as i, (k, v).

More dictionary guides:

Frequently asked questions

How do I iterate through a dictionary in Python?

Loop over .items(): for key, value in my_dict.items():. That gives you both parts on every pass without a second lookup.

Why does looping a dictionary give only the keys?

Because for k in d is shorthand for for k in d.keys(). Use d.items() when you want the values as well.

What is the difference between keys(), values() and items()?

keys() gives the keys, values() the values and items() both as (key, value) pairs. All three are live views rather than lists.

Why do I get RuntimeError: dictionary changed size during iteration?

You added or removed a key inside the loop. Iterate over a snapshot with for k in list(d):, or build a new dictionary with a comprehension.

Can I change values while iterating a dictionary?

Yes. Updating a value does not change the dictionary’s size, so the loop is unaffected. Only adding or deleting keys raises.

How do I loop through a dictionary in sorted order?

sorted(d.items()) sorts by key. For values, use sorted(d.items(), key=lambda pair: pair[1]), with reverse=True for descending.

Are Python dictionaries ordered?

Yes, since Python 3.7 they keep insertion order, and reversed(d) walks it backwards. The view objects are described in the Python dictionary view objects documentation.