Dictionaries
Table of Contents
Dictionaries are Python’s built-in hash map — they store key-value pairs and give you O(1) lookups by key. If lists are for ordered sequences, dictionaries are for labeled data: user profiles, configuration, API responses, anything where you access values by name rather than position.
Creating Dictionaries
# Empty dict
empty = {}
# Dict with values
user = {
"name": "Alice",
"email": "alice@example.com",
"age": 28,
"active": True
}
# From a list of tuples
pairs = [("a", 1), ("b", 2), ("c", 3)]
d = dict(pairs) # {'a': 1, 'b': 2, 'c': 3}
# From keyword arguments
config = dict(host="localhost", port=5432, debug=True)
Accessing Values
user = {"name": "Alice", "email": "alice@example.com", "age": 28}
# Bracket notation (raises KeyError if key missing)
print(user["name"]) # Alice
# .get() (returns None or a default if key missing)
print(user.get("phone")) # None
print(user.get("phone", "N/A")) # N/A
Use .get() when the key might not exist. Use brackets when you’re certain it does (or want an error if it doesn’t).
Adding, Updating, and Deleting
user = {"name": "Alice"}
# Add or update
user["email"] = "alice@example.com"
user["name"] = "Alice Smith" # update existing
# Update multiple keys at once
user.update({"age": 28, "role": "admin"})
# Delete
del user["role"] # raises KeyError if missing
removed = user.pop("age") # removes and returns value (28)
removed = user.pop("phone", None) # returns None if missing (no error)
# Remove last inserted item
last_key, last_value = user.popitem()
Checking Keys
user = {"name": "Alice", "email": "alice@example.com"}
# `in` checks keys (not values)
print("name" in user) # True
print("phone" in user) # False
print("Alice" in user) # False (checks keys, not values)
Iterating
scores = {"math": 95, "english": 88, "science": 92}
# Keys (default)
for subject in scores:
print(subject)
# Values
for score in scores.values():
print(score)
# Both
for subject, score in scores.items():
print(f"{subject}: {score}")
Dictionary Comprehensions
Like list comprehensions, but for dicts:
# Basic
squares = {x: x**2 for x in range(6)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
# With filtering
words = ["hello", "world", "python", "code"]
lengths = {w: len(w) for w in words if len(w) > 4}
# {'hello': 5, 'world': 5, 'python': 6}
# Inverting a dict
original = {"a": 1, "b": 2, "c": 3}
inverted = {v: k for k, v in original.items()}
# {1: 'a', 2: 'b', 3: 'c'}
Nested Dictionaries
users = {
"alice": {
"email": "alice@example.com",
"preferences": {"theme": "dark", "language": "en"}
},
"bob": {
"email": "bob@example.com",
"preferences": {"theme": "light", "language": "fr"}
}
}
# Access nested values
print(users["alice"]["preferences"]["theme"]) # dark
# Safe nested access
theme = users.get("charlie", {}).get("preferences", {}).get("theme", "light")
print(theme) # light (default, since charlie doesn't exist)
Merging Dictionaries
defaults = {"theme": "light", "font_size": 14, "language": "en"}
user_prefs = {"theme": "dark", "font_size": 16}
# Spread operator (Python 3.9+)
merged = {**defaults, **user_prefs}
# {'theme': 'dark', 'font_size': 16, 'language': 'en'}
# Union operator (Python 3.9+)
merged = defaults | user_prefs
# In-place merge
defaults.update(user_prefs)
Common Patterns
Counting occurrences
text = "hello world hello python hello"
words = text.split()
# Manual counting
counts = {}
for word in words:
counts[word] = counts.get(word, 0) + 1
print(counts) # {'hello': 3, 'world': 1, 'python': 1}
# Using Counter (preferred)
from collections import Counter
counts = Counter(words)
print(counts.most_common(2)) # [('hello', 3), ('world', 1)]
Grouping items
people = [
{"name": "Alice", "dept": "Engineering"},
{"name": "Bob", "dept": "Marketing"},
{"name": "Charlie", "dept": "Engineering"},
{"name": "Diana", "dept": "Marketing"},
]
from collections import defaultdict
by_dept = defaultdict(list)
for person in people:
by_dept[person["dept"]].append(person["name"])
print(dict(by_dept))
# {'Engineering': ['Alice', 'Charlie'], 'Marketing': ['Bob', 'Diana']}
Building a lookup table
users_list = [
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Bob"},
{"id": 3, "name": "Charlie"},
]
# O(1) lookup by ID instead of O(n) list search
users_by_id = {u["id"]: u for u in users_list}
print(users_by_id[2]["name"]) # Bob
Caching / memoization
cache = {}
def expensive_calculation(n):
if n in cache:
return cache[n]
# Simulate expensive work
result = sum(range(n))
cache[n] = result
return result
setdefault
Initialize a key only if it doesn’t exist:
inventory = {}
# Without setdefault
if "apples" not in inventory:
inventory["apples"] = []
inventory["apples"].append("Granny Smith")
# With setdefault (one line)
inventory.setdefault("oranges", []).append("Navel")
Ordering
Since Python 3.7, dictionaries maintain insertion order:
d = {"b": 2, "a": 1, "c": 3}
print(list(d.keys())) # ['b', 'a', 'c'] — insertion order preserved
# Sort by key
sorted_d = dict(sorted(d.items()))
# {'a': 1, 'b': 2, 'c': 3}
# Sort by value
sorted_by_val = dict(sorted(d.items(), key=lambda item: item[1]))
# {'a': 1, 'b': 2, 'c': 3}
Performance
| Operation | Time Complexity |
|---|---|
d[key] (get) |
O(1) average |
d[key] = value (set) |
O(1) average |
key in d |
O(1) average |
del d[key] |
O(1) average |
| Iterating all items | O(n) |
Dictionaries are implemented as hash tables. Keys must be hashable (immutable): strings, numbers, tuples. Lists and other dicts cannot be keys.
What’s Next
Dictionaries and lists together handle most data manipulation in Python. From here, explore classes and OOP to create your own data types, or dive into file I/O to read and write data from disk.