Technology  /  Python

๐Ÿ Python 78 guides ยท updated 2026

From first variable to OOP, generators, and real projects โ€” the language that runs everything from data pipelines to AI agents, taught the practical way.

Python Dictionary Deep Dive: Hashing, Ordering, and Advanced Patterns

A Python dictionary is a hash map โ€” it maps keys to values using a hash function to find storage positions. Understanding what this means in practice helps you use dicts more effectively and avoid subtle bugs around hashability and performance.

Creating Dictionaries

# Direct literal
person = {"name": "Alice", "age": 30, "role": "engineer"}
# dict() constructor โ€” from keyword arguments
settings = dict(host="localhost", port=5432, debug=False)
# dict() from an iterable of key-value pairs
pairs = [("a", 1), ("b", 2), ("c", 3)]
from_pairs = dict(pairs)
# Dict comprehension
squares = {x: x**2 for x in range(1, 6)}
print(squares) # {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
# Filter with comprehension
high_scores = {k: v for k, v in {"Alice": 85, "Bob": 92, "Carol": 78}.items() if v >= 90}
print(high_scores) # {'Bob': 92}

Dict Ordering in Python 3.7+

Since Python 3.7, dicts maintain insertion order. This is guaranteed by the language specification, not just a CPython implementation detail.

d = {}
d["third"] = 3
d["first"] = 1
d["second"] = 2
print(list(d.keys())) # ['third', 'first', 'second'] โ€” insertion order
print(list(d.values())) # [3, 1, 2]
print(list(d.items())) # [('third', 3), ('first', 1), ('second', 2)]

Accessing and Modifying

config = {"timeout": 30, "retries": 3, "debug": False}
# Access โ€” raises KeyError if missing
print(config["timeout"]) # 30
# Safe access with default
print(config.get("host", "localhost")) # "localhost" โ€” no error
# setdefault โ€” set and return default if key missing, otherwise return existing
config.setdefault("port", 5432) # adds port=5432 if not present
print(config["port"]) # 5432
# Update from another dict
config.update({"timeout": 60, "log_level": "INFO"})
print(config)

Merging Dictionaries

Python 3.9 introduced the | merge operator:

defaults = {"color": "blue", "size": "medium", "weight": 1.0}
user_prefs = {"color": "red", "size": "large"}
# Python 3.9+ โ€” creates a new merged dict, right side wins on conflicts
merged = defaults | user_prefs
print(merged)
# {'color': 'red', 'size': 'large', 'weight': 1.0}
# In-place update
defaults |= user_prefs # modifies defaults in place
# Pre-3.9 equivalent
merged_old = {**defaults, **user_prefs}

Common Dict Patterns

Grouping items by a key:

from collections import defaultdict
transactions = [
{"user": "Alice", "amount": 50},
{"user": "Bob", "amount": 30},
{"user": "Alice", "amount": 75},
{"user": "Bob", "amount": 20},
]
by_user = defaultdict(list)
for t in transactions:
by_user[t["user"]].append(t["amount"])
print(dict(by_user))
# {'Alice': [50, 75], 'Bob': [30, 20]}
# Totals per user
totals = {user: sum(amounts) for user, amounts in by_user.items()}
print(totals) # {'Alice': 125, 'Bob': 50}

Inverting a dict (swap keys and values):

original = {"a": 1, "b": 2, "c": 3}
inverted = {v: k for k, v in original.items()}
print(inverted) # {1: 'a', 2: 'b', 3: 'c'}
# Note: only works cleanly when values are unique and hashable

Safely accessing nested dicts:

# Manual approach
data = {"user": {"profile": {"name": "Alice"}}}
name = data.get("user", {}).get("profile", {}).get("name", "Unknown")
print(name) # "Alice"
# Using a helper
def safe_get(d, *keys, default=None):
"""Safely traverse a nested dict."""
for key in keys:
if not isinstance(d, dict):
return default
d = d.get(key, default)
return d
print(safe_get(data, "user", "profile", "name")) # "Alice"
print(safe_get(data, "user", "address", "city")) # None

Iterating Correctly

inventory = {"apple": 5, "banana": 3, "cherry": 12}
# Keys (default iteration)
for key in inventory:
print(key)
# Values
for value in inventory.values():
print(value)
# Key-value pairs โ€” the most common pattern
for item, count in inventory.items():
print(f"{item}: {count}")
# Do NOT modify a dict while iterating โ€” raises RuntimeError
# Correct approach: iterate over a copy
for key in list(inventory.keys()):
if inventory[key] < 5:
del inventory[key] # safe โ€” iterating over a copy of keys

What Keys Must Be

Dict keys must be hashable. Strings, numbers, tuples of hashable objects โ€” all fine. Lists and dicts โ€” not allowed.

# Valid keys
d = {
"string_key": 1,
42: 2,
(1, 2): 3, # tuple of ints โ€” hashable
True: 4, # bool is hashable (note: True == 1, so True and 1 are the same key)
}
# Invalid
try:
bad = {[1, 2]: "value"} # list is not hashable
except TypeError as e:
print(f"Cannot use list as key: {e}")

Note: True == 1 in Python, so {True: "a", 1: "b"} contains only one key with value "b".