The dict.fromkeys(iterable[, value=None]) class method creates a new dictionary whose keys are drawn from an iterable, with all keys initialized to the specified value (defaulting to None).
The Shared Mutable Pitfall: When passing a mutable object (such as an empty list [] or sub-dictionary {}) as the default value, all keys share the exact same object reference in memory. Modifying the list for one key will mutate it for all keys. Use a dictionary comprehension when each key requires an independent container.
The standard CPython signature for dict.fromkeys() is:
dict.fromkeys(iterable, value=None)
| Parameter | Type | Default | Behavior & Description |
|---|---|---|---|
iterable |
Iterable[Hashable] |
Required | An iterable yielding unique, hashable objects to be used as dictionary keys (e.g., list, tuple, set, range, string). |
value |
Any object | None |
The initial value assigned to every key. If omitted, defaults to None. |
| Return Value | dict |
— | A new dictionary populated with keys from the iterable, all initialized to value. |
You can initialize keys with the default None or provide an immutable primitive such as an integer, string, or boolean:
fields = ["name", "email", "phone"]
# 1. Without value parameter: defaults to None
empty_record = dict.fromkeys(fields)
print("Default None: ", empty_record)
# Output: {'name': None, 'email': None, 'phone': None}
# 2. With immutable default value
flags = dict.fromkeys(["is_admin", "is_verified", "is_banned"], False)
print("Boolean flags: ", flags)
# Output: {'is_admin': False, 'is_verified': False, 'is_banned': False}
One of Python's most notorious beginner bugs occurs when passing a mutable container (such as a list [] or dictionary {}) to dict.fromkeys(). Because Python evaluates the argument once, every key references the exact same list in memory:
# BUG: All keys reference the SAME empty list!
buggy_dict = dict.fromkeys(["team_a", "team_b"], [])
# Append a player to team_a
buggy_dict["team_a"].append("Alice")
# Disaster: team_b was also modified!
print("team_a:", buggy_dict["team_a"]) # ['Alice']
print("team_b:", buggy_dict["team_b"]) # ['Alice'] (unintended side effect!)
print("Identity check:", buggy_dict["team_a"] is buggy_dict["team_b"]) # True
Whenever you need each key to possess an independent, fresh container, use a dictionary comprehension instead of fromkeys(). The comprehension instantiates a new object on every iteration:
# Each key gets its own unique, newly instantiated list
clean_dict = {k: [] for k in ["team_a", "team_b"]}
clean_dict["team_a"].append("Alice")
print("team_a:", clean_dict["team_a"]) # ['Alice']
print("team_b:", clean_dict["team_b"]) # [] (properly empty!)
print("Identity check:", clean_dict["team_a"] is clean_dict["team_b"]) # False
dict.fromkeys() accepts any iterable yielding hashable objects, including ranges, strings, and generator expressions:
# 1. From a numeric range
counters = dict.fromkeys(range(1, 5), 0)
print("Range keys: ", counters) # {1: 0, 2: 0, 3: 0, 4: 0}
# 2. From a string (splits into individual character keys)
char_presence = dict.fromkeys("aeiou", True)
print("Vowel lookup:", char_presence) # {'a': True, 'e': True, 'i': True, 'o': True, 'u': True}
The standard CPython hash table implementation preserves insertion order. Calling dict.fromkeys() deduplicates sequences in O(N) time while preserving order (unlike set() which randomizes order):
def deduplicate_preserve_order(items: list) -> list:
return list(dict.fromkeys(items))
raw_tags = ["python", "fastapi", "python", "docker", "fastapi"]
unique_tags = deduplicate_preserve_order(raw_tags)
print("Clean tags:", unique_tags) # ['python', 'fastapi', 'docker']
Initialize uniform default templates for database rows or API response models:
def create_record_template(columns: list[str], default_val="") -> dict:
return dict.fromkeys(columns, default_val)
user_columns = ["id", "first_name", "last_name", "phone"]
template = create_record_template(user_columns, None)
print("Template blueprint:", template)
Never write dict.fromkeys(keys, []) or dict.fromkeys(keys, {}). The single list or dictionary instance is shared across every key. Use {k: [] for k in keys} instead.
Strings are iterables in Python. Calling dict.fromkeys("user_id", 0) does not create a dictionary with the key "user_id" — it creates keys 'u', 's', 'e', 'r', '_', 'i', 'd'. Wrap single strings in a list: dict.fromkeys(["user_id"], 0).
Run this complete test harness verifying default assignments, deduplication, and mutable independence:
class FromkeysVerifier:
@staticmethod
def init_blueprint(fields: list, default_value=None) -> dict:
return dict.fromkeys(fields, default_value)
@staticmethod
def deduplicate_seq(seq: list) -> list:
return list(dict.fromkeys(seq))
# Verification unit assertions
v = FromkeysVerifier()
# Test 1: None default initialization
d1 = v.init_blueprint(["a", "b"])
assert d1 == {"a": None, "b": None}
# Test 2: Custom immutable default
d2 = v.init_blueprint(["x", "y"], 0)
assert d2 == {"x": 0, "y": 0}
# Test 3: Fast order-preserving deduplication
raw_data = [1, 2, 1, 3, 2, 4]
assert v.deduplicate_seq(raw_data) == [1, 2, 3, 4]
print("All dict.fromkeys() unit assertions passed successfully!")
Open in Google Colab View on GitHub
Run all dictionary fromkeys exercises interactively, test default initialization, observe the shared mutable bug and its comprehension solution, and verify assertions in Google Colab.
Author & Instructor at plus2net
I write and maintain practical tutorials on Python, PHP, SQL, JavaScript, HTML, jQuery, and web development at plus2net. The tutorials focus on clear explanations, working examples, and code that readers can test and adapt while learning.