Skip to main content

Dictionaries and Nested Records

What You'll Learn

How to create, access, and manipulate dictionaries — Python's most important data structure for real-world data.

What Is a Dictionary?

A dictionary stores key-value pairs. Keys must be unique and immutable (strings, numbers, tuples). Values can be anything.

user = {
"name": "Alice",
"age": 30,
"email": "alice@example.com",
"active": True,
}

Think of it as a lookup table: given a key, get a value instantly (O(1)).

Creating Dictionaries

# Literal
person = {"name": "Bob", "age": 25}

# Empty
config = {}

# From key-value pairs
pairs = [("a", 1), ("b", 2), ("c", 3)]
d = dict(pairs)

# From keyword arguments
d = dict(name="Alice", age=30)

# Dict comprehension
squares = {n: n**2 for n in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

Accessing Values

user = {"name": "Alice", "age": 30}

# Direct access — raises KeyError if key missing
print(user["name"]) # Alice

# .get() — returns None (or default) if key missing
print(user.get("email")) # None
print(user.get("email", "N/A")) # N/A

# Check if key exists
print("name" in user) # True
print("phone" in user) # False

Modifying Dictionaries

user = {"name": "Alice", "age": 30}

# Add or update
user["email"] = "alice@example.com"
user["age"] = 31

# Update multiple at once
user.update({"city": "London", "age": 32})

# Remove
del user["age"] # raises KeyError if missing
email = user.pop("email") # remove and return value
user.pop("phone", None) # no error if key missing
user.clear() # remove all

Iterating Over Dictionaries

scores = {"Alice": 95, "Bob": 82, "Charlie": 74}

# Keys only
for name in scores:
print(name)

# Values only
for score in scores.values():
print(score)

# Key and value together
for name, score in scores.items():
print(f"{name}: {score}")

# Output:
# Alice: 95
# Bob: 82
# Charlie: 74

Useful Dictionary Methods

d = {"a": 1, "b": 2, "c": 3}

d.keys() # dict_keys(['a', 'b', 'c'])
d.values() # dict_values([1, 2, 3])
d.items() # dict_items([('a', 1), ('b', 2), ('c', 3)])

# setdefault — set a key only if it doesn't exist
d.setdefault("d", 4) # adds d=4
d.setdefault("a", 99) # a stays 1

# Merge two dicts (Python 3.9+)
defaults = {"timeout": 30, "retries": 3}
overrides = {"timeout": 60}
config = defaults | overrides
# {"timeout": 60, "retries": 3}

Nested Dictionaries

Real-world data often has nested structures:

users = {
"alice": {
"email": "alice@example.com",
"scores": [95, 88, 91],
"address": {
"city": "London",
"country": "UK",
}
},
"bob": {
"email": "bob@example.com",
"scores": [72, 85, 78],
"address": {
"city": "Paris",
"country": "FR",
}
}
}

# Access nested values
print(users["alice"]["address"]["city"]) # London
print(users["bob"]["scores"][0]) # 72

# Safe access with .get()
city = users.get("charlie", {}).get("address", {}).get("city", "Unknown")
print(city) # Unknown

Dict Comprehensions

# Invert a dictionary
original = {"a": 1, "b": 2, "c": 3}
inverted = {v: k for k, v in original.items()}
# {1: 'a', 2: 'b', 3: 'c'}

# Filter a dictionary
scores = {"Alice": 95, "Bob": 62, "Charlie": 74}
passing = {name: score for name, score in scores.items() if score >= 70}
# {'Alice': 95, 'Charlie': 74}

# Transform values
doubled = {k: v * 2 for k, v in original.items()}
# {'a': 2, 'b': 4, 'c': 6}

defaultdict — Never Hit a Missing Key

from collections import defaultdict

# Automatically creates missing keys with a default value
word_count = defaultdict(int) # default = 0

words = ["apple", "banana", "apple", "cherry", "banana", "apple"]
for word in words:
word_count[word] += 1 # no KeyError even on first access

print(dict(word_count))
# {'apple': 3, 'banana': 2, 'cherry': 1}

Another common pattern — grouping:

from collections import defaultdict

people = [
("Alice", "Engineering"),
("Bob", "Marketing"),
("Charlie", "Engineering"),
("Diana", "Marketing"),
]

by_dept = defaultdict(list)
for name, dept in people:
by_dept[dept].append(name)

# {'Engineering': ['Alice', 'Charlie'], 'Marketing': ['Bob', 'Diana']}

Counter — Count Things Easily

from collections import Counter

words = ["apple", "banana", "apple", "cherry", "banana", "apple"]
counts = Counter(words)

print(counts)
# Counter({'apple': 3, 'banana': 2, 'cherry': 1})

print(counts.most_common(2))
# [('apple', 3), ('banana', 2)]

# Counters support arithmetic
a = Counter({"x": 3, "y": 1})
b = Counter({"x": 1, "z": 2})
print(a + b) # Counter({'x': 4, 'z': 2, 'y': 1})

Common Patterns

# Count occurrences (manual)
counts = {}
for item in items:
counts[item] = counts.get(item, 0) + 1

# Group by a key
groups = {}
for item in data:
key = item["department"]
groups.setdefault(key, []).append(item)

# Frequency table
from collections import Counter
freq = Counter(items)

Common Mistakes

MistakeFix
d["missing_key"]Use d.get("key", default)
Iterating while modifyingIterate over list(d.keys())
Mutating a shared dict accidentallyUse d.copy() or dict(d) for a shallow copy
{} for empty setUse set(){} makes an empty dict

Quick Reference

# Create
d = {}
d = {"key": "value"}
d = dict(key="value")
d = {k: v for k, v in pairs}

# Access
d["key"]
d.get("key", default)
"key" in d

# Modify
d["key"] = value
d.update(other_dict)
del d["key"]
d.pop("key", None)

# Iterate
for k in d:
for v in d.values():
for k, v in d.items():

# Merge (3.9+)
merged = d1 | d2

# Useful collections
from collections import defaultdict, Counter

What's Next

Lesson 3: Iteration, Comprehensions, and Generators