dict.keys()
Iterate the dict's keys as a live view — not a copy. Insertion order preserved (3.7+).
Common call
for k in d.keys():
Returns
a dict_keys view — live, not a list
Replaces
the manual key list; iterating d directly does the same
Watch out
mutating d during iteration raises RuntimeError
dict.keys()
→ dict_keys
Demo
Live evaluation
Try:
Inputs
dictdictkey: value pairs
Output
{'a': '1', 'b': '2', 'c': '3'}.keys()
['a', 'b', 'c']
The demo materializes the view as a list so you can see the keys at once. In real code you iterate directly: `for k in d.keys():` — or, equivalently, `for k in d:`. Since Python 3.7 the order matches insertion order; earlier versions gave no guarantee.
Common patterns
Iterate keys explicitly
When the code needs to say "keys" out loud for readability, use .keys(). `for k in d:` does the same thing.
for k in d.keys(): process(k)
Compare two dicts' keys
keys() views are set-like — union, intersection, and difference all work.
shared = d1.keys() & d2.keys() only_in_1 = d1.keys() - d2.keys()
Snapshot for concurrent mutation
Wrap in list() when you need to modify the dict during iteration.
for k in list(d.keys()): if predicate(k): del d[k]
Examples
1. Iterate keys
list({"a": 1, "b": 2}.keys())
Returns
["a", "b"]2. Empty dict
list({}.keys())
Returns
[]3. Insertion order
list({"z": 1, "a": 2}.keys())
Returns
["z", "a"]4. Set-like intersection
{"a": 1, "b": 2}.keys() & {"b", "c"}
Returns
{"b"}5. View reflects updates
d = {"a": 1}
k = d.keys()
d["b"] = 2
list(k)
Returns
["a", "b"]Pitfalls
1. It is a VIEW, not a list
keys() returns a live view. Type checks that expect list, or indexing, both fail. Wrap in list() when you need a snapshot or index access.
Not indexable
d = {"a": 1, "b": 2} d.keys()[0]
TypeError: 'dict_keys' object is not subscriptable
Materialize
list(d.keys())[0]
"a"
2. Modifying the dict during iteration raises
Changing the dict's size while iterating over its keys view is a RuntimeError. Read-only iteration is safe; add/remove keys — take a snapshot first.
Runtime error
for k in d.keys(): if d[k] is None: del d[k]
RuntimeError: dictionary changed size during iteration
Snapshot first
for k in list(d.keys()): if d[k] is None: del d[k]
safe
3. `for k in d.keys()` is the same as `for k in d`
Iterating a dict yields its keys — .keys() is redundant here. Use whichever reads more clearly for the caller; there is no performance difference.
Verbose
for k in d.keys(): print(k)
same as below
Idiomatic
for k in d: print(k)
same behavior
4. Keys view lives with the dict
Keeping a reference to keys() does not freeze the dict. Later mutations show up when you iterate the same view again — surprising if you thought you had a snapshot.
Not a snapshot
k = d.keys() d["new"] = 99 list(k) # includes "new"
view sees the added key
Snapshot with list
snap = list(d.keys()) d["new"] = 99 snap # unchanged
independent copy
When to use
Use it
- Explicitly reading "keys" for code clarity
- Set-like operations across two dicts (keys views are set-like)
- Feeding sorted / filter / any / all with just the keys
- Membership tests: `k in d.keys()` is equivalent to `k in d`
Reach for something else
- Simple iteration → for k in d (no need to call .keys())
- Index access → list(d.keys())
- Freezing a snapshot for concurrent mutation → list(d.keys())
- You need values too → d.values() or d.items()
Notes
Complexity
O(1) to create the view; O(n) to iterate
Return
dict_keys view — live, sized, iterable, set-like
CPython impl
Objects/dictobject.c :: dictkeys_new — no data copied
Memory
O(1) — the view is a small wrapper over the dict
Thread-safe
Iteration is not safe under concurrent mutation of the source dict
FAQ
Nothing — iterating a dict yields its keys by default. `d.keys()` is only needed when you want set-like operations (`&`, `|`, `-`) between two dicts' key sets, or when the extra noun helps readability.
History
2.2
keys() introduced (originally as a list-building method).
3.0
keys() became a view instead of a list; iterkeys() was removed.
3.7
Insertion order preserved by dict — keys() iterates in that order.