Backend / Python core / Tricky questions / 44_dict_fromkeys_shared_default.md

dict.fromkeys shares a single default object

Updated 3 min read source
On this page9
  1. The gotcha
  2. Minimal repro
  3. Why it happens
  4. Variants and confusion
  5. How to avoid
  6. Real-world bug
  7. When dict.fromkeys is fine
  8. A historical note: set.fromkeys doesn’t exist
  9. Interview angle

dict.fromkeys shares a single default object

The gotcha

dict.fromkeys(keys, default) evaluates default once and assigns the same object to every key. If default is mutable, mutating one entry mutates all of them.

Minimal repro

python
d = dict.fromkeys(['a', 'b', 'c'], [])
d['a'].append(1)
print(d)        # {'a': [1], 'b': [1], 'c': [1]}    ← all share the same list

The empty list literal [] is created once, before dict.fromkeys runs. The dict ends up with three keys all referencing the same list. Append to one → visible everywhere.

Why it happens

This is the same trap as mutable default arguments. dict.fromkeys doesn’t have a “factory” parameter — it takes a value. Python evaluates the value expression once, passes the result, and the method assigns the same reference to every key.

python
# Conceptually:
def fromkeys(keys, default=None):
    # `default` is the same object for all
    return {k: default for k in keys}

Variants and confusion

For immutable defaults (the common case), no problem:

python
dict.fromkeys(['a', 'b', 'c'], 0)        # {'a': 0, 'b': 0, 'c': 0}
dict.fromkeys(['a', 'b', 'c'], "init")   # {'a': 'init', 'b': 'init', 'c': 'init'}

Reassigning is fine — d['a'] = 5 rebinds the key without affecting others, because integers are immutable. The shared-reference issue only manifests when the value is mutated in place.

python
d = dict.fromkeys(['a', 'b'], 0)
d['a'] = 5                               # OK — rebinds 'a'
print(d)                                 # {'a': 5, 'b': 0}

How to avoid

Dict comprehension with explicit creation

python
d = {k: [] for k in ['a', 'b', 'c']}
d['a'].append(1)
print(d)        # {'a': [1], 'b': [], 'c': []}

Each iteration creates a fresh []. No sharing.

defaultdict for the “compute on access” pattern

python
from collections import defaultdict
d = defaultdict(list)
d['a'].append(1)
d['b'].append(2)
print(dict(d))  # {'a': [1], 'b': [2]}

Factory called per access. Same fix as for the mutable default arguments gotcha.

Loop with explicit copy

python
template = {"items": [], "count": 0}
# one fresh dict per key
d = {k: dict(template) for k in keys}
# But values inside template are still shared if mutable! Need deepcopy:
import copy
d = {k: copy.deepcopy(template) for k in keys}

Real-world bug

python
# Initialize a per-user errors collector:
USERS = ['alice', 'bob', 'carol']
errors = dict.fromkeys(USERS, [])

errors['alice'].append("login failed")
print(errors)
# {'alice': ['login failed'], 'bob': ['login failed'], 'carol': ['login failed']}

Now every user has the same error. Bug.

Fix:

python
errors = {u: [] for u in USERS}

When dict.fromkeys is fine

When all keys legitimately should reference the same object (rare), or when the default is immutable:

python
# Counter initialization with 0 — fine:
seen = dict.fromkeys(items, 0)
for item in items:
    seen[item] += 1
python
# Setting all flags to the same reference (deliberately):
shared_state = SharedState()
flags = dict.fromkeys(['x', 'y', 'z'], shared_state)
# All three keys point to the same SharedState — intentional

A historical note: set.fromkeys doesn’t exist

python
set.fromkeys([1, 2, 3])     # AttributeError

Use a set literal or comprehension:

python
{1, 2, 3}
{x for x in range(3)}

Sets don’t have key/value pairs, so fromkeys makes no sense for them.

Interview angle 4

  • Q: “What does dict.fromkeys(['a', 'b', 'c'], []) produce, and what’s the trap?” — three keys sharing one list. Mutating one mutates all.
  • Q: “How do you fix it?” — dict comprehension with {k: [] for k in keys}, or defaultdict(list).
  • Follow-up: “Why does this happen?” — [] evaluates once; fromkeys binds same object to every key.
  • Follow-up: “Is this a problem with dict.fromkeys(keys, 0)?” — no, integers are immutable; reassignment doesn’t mutate.

See Mutable default arguments, Dataclass mutable defaults, collections — specialized containers.