Python Dictionaries

Master Python dicts: creation, methods, comprehensions, merging, and the powerful defaultdict and Counter types.

Beginner 12 min read 10 examples

Creating Dictionaries

Dictionaries store key-value pairs. Keys must be unique and hashable. Values can be any type. As of Python 3.7, dicts maintain insertion order.

Python
# Literal syntax
person = {
    "name":  "Alice",
    "age":   30,
    "city":  "London",
}

# dict() constructor with keyword arguments
config = dict(host="localhost", port=5432, debug=False)

# dict() from list of key-value pairs
pairs = [("a", 1), ("b", 2), ("c", 3)]
d = dict(pairs)     # {'a': 1, 'b': 2, 'c': 3}

# Empty dict
empty = {}
empty2 = dict()

# Keys can be any hashable type
mixed_keys = {
    "string":  1,
    42:        "number key",
    (1, 2):    "tuple key",
    True:      "bool key",    # True == 1, so this replaces key 1 (!)
}

# Nested dicts
user = {
    "name": "Bob",
    "address": {
        "street": "123 Main St",
        "city": "New York",
    },
    "scores": [95, 87, 92],
}
print(user["address"]["city"])      # New York
print(user["scores"][0])            # 95

# Check size and membership (checks keys by default)
print(len(person))          # 3
print("name" in person)     # True  - key exists
print("Alice" in person)    # False - checks keys, not values
print("age" not in person)  # False

Accessing and Modifying

Python
person = {"name": "Alice", "age": 30}

# Access by key - raises KeyError if not found
print(person["name"])   # Alice
# print(person["xyz"]) # KeyError: 'xyz'

# get() - safe access with optional default
print(person.get("name"))           # Alice
print(person.get("email"))          # None     (no KeyError)
print(person.get("email", "N/A"))   # N/A      (custom default)

# Add or update
person["city"] = "London"           # add new key
person["age"]  = 31                 # update existing key

# setdefault() - set only if key does not exist
person.setdefault("country", "UK")  # adds "country": "UK"
person.setdefault("age", 99)        # age stays 31, not changed
print(person["country"])    # UK
print(person["age"])        # 31

# Delete
del person["city"]                  # raises KeyError if not found
value = person.pop("country")       # remove and return; KeyError if missing
value2 = person.pop("xyz", None)    # safe pop with default

print(person)   # {'name': 'Alice', 'age': 31}

Dictionary Methods

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

# Views - dynamic (reflect changes)
print(d.keys())     # dict_keys(['a', 'b', 'c'])
print(d.values())   # dict_values([1, 2, 3])
print(d.items())    # dict_items([('a', 1), ('b', 2), ('c', 3)])

# Convert to lists if needed
keys_list = list(d.keys())

# update() - merge another dict into this one (in place)
d.update({"c": 30, "d": 4})    # c overwritten, d added
print(d)    # {'a': 1, 'b': 2, 'c': 30, 'd': 4}

d.update(e=5, f=6)              # keyword argument form
print(d)    # {'a': 1, 'b': 2, 'c': 30, 'd': 4, 'e': 5, 'f': 6}

# pop() - remove and return
val = d.pop("f")
val2 = d.pop("xyz", "missing")  # safe with default
print(val, val2)    # 6 missing

# popitem() - remove and return an arbitrary (last in 3.7+) item
key, value = d.popitem()
print(key, value)   # f 6 (last inserted)

# copy() - shallow copy
original = {"x": [1, 2, 3], "y": 10}
shallow  = original.copy()
shallow["y"] = 99               # does not affect original
shallow["x"].append(4)          # DOES affect original (shared list)
print(original)    # {'x': [1, 2, 3, 4], 'y': 10}

# clear()
temp = {"k": "v"}
temp.clear()
print(temp)     # {}
MethodDescriptionReturns
get(key, default)Safe key accessvalue or default
setdefault(key, val)Set if key absentexisting or new value
update(other)Merge in placeNone
pop(key, default)Remove and returnvalue
popitem()Remove last item(key, value)
keys()Key viewdict_keys
values()Value viewdict_values
items()Key-value pair viewdict_items
copy()Shallow copydict
clear()Remove all itemsNone

Iterating Dictionaries

Python
scores = {"Alice": 95, "Bob": 87, "Charlie": 92}

# Iterate over keys (default)
for name in scores:
    print(name)     # Alice, Bob, Charlie

# Iterate over values
for score in scores.values():
    print(score)    # 95, 87, 92

# Iterate over key-value pairs (most common)
for name, score in scores.items():
    print(f"{name}: {score}")
# Alice: 95
# Bob: 87
# Charlie: 92

# Sorted iteration
for name in sorted(scores):
    print(f"{name}: {scores[name]}")

# Sort by value (highest first)
for name, score in sorted(scores.items(), key=lambda x: x[1], reverse=True):
    print(f"{name}: {score}")
# Alice: 95
# Charlie: 92
# Bob: 87

# Filter while iterating - use comprehension to avoid RuntimeError
# Never modify a dict while iterating over it directly
active_users = {"Alice": True, "Bob": False, "Charlie": True}
active = {k: v for k, v in active_users.items() if v}
print(active)   # {'Alice': True, 'Charlie': True}

Dict Comprehensions

Python
# Basic: {key_expr: val_expr for item in iterable}
squares = {x: x**2 for x in range(1, 6)}
print(squares)  # {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

# With condition
even_squares = {x: x**2 for x in range(1, 11) if x % 2 == 0}
print(even_squares)  # {2: 4, 4: 16, 6: 36, 8: 64, 10: 100}

# Transform an existing dict
prices = {"apple": 0.5, "banana": 0.3, "cherry": 1.2}
with_tax = {k: round(v * 1.1, 2) for k, v in prices.items()}
print(with_tax)  # {'apple': 0.55, 'banana': 0.33, 'cherry': 1.32}

# Invert 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'}

# Build dict from two lists using zip
keys   = ["name", "age", "city"]
values = ["Alice", 30, "London"]
person = {k: v for k, v in zip(keys, values)}
# Simpler: dict(zip(keys, values))
print(person)   # {'name': 'Alice', 'age': 30, 'city': 'London'}

# Group items by a property
words = ["apple", "banana", "cherry", "avocado", "blueberry"]
by_first_letter = {}
for word in words:
    by_first_letter.setdefault(word[0], []).append(word)
print(by_first_letter)
# {'a': ['apple', 'avocado'], 'b': ['banana', 'blueberry'], 'c': ['cherry']}

Merging Dictionaries

Python
defaults = {"color": "blue", "size": "medium", "font": "Arial"}
overrides = {"color": "red", "weight": "bold"}

# Python 3.9+: | operator (creates new dict)
merged = defaults | overrides
print(merged)
# {'color': 'red', 'size': 'medium', 'font': 'Arial', 'weight': 'bold'}
# overrides win on conflict (color: 'red', not 'blue')

# Python 3.9+: |= operator (updates in place)
config = {"debug": False, "port": 8080}
config |= {"port": 9000, "host": "localhost"}
print(config)
# {'debug': False, 'port': 9000, 'host': 'localhost'}

# Python 3.5+: ** unpacking (works in all Python 3 versions)
merged2 = {**defaults, **overrides}    # same result as |
print(merged2)

# Multiple dicts
base    = {"a": 1}
layer1  = {"b": 2}
layer2  = {"c": 3, "a": 99}
combined = {**base, **layer1, **layer2}
print(combined)     # {'a': 99, 'b': 2, 'c': 3}  - last wins

# update() - modifies in place
target = {"a": 1, "b": 2}
source = {"b": 20, "c": 30}
target.update(source)
print(target)   # {'a': 1, 'b': 20, 'c': 30}

defaultdict and Counter

The collections module provides specialized dict subclasses for common patterns.

Python
from collections import defaultdict, Counter, OrderedDict

# --- defaultdict ---
# Never raises KeyError - creates default value for missing keys

# Group words by first letter
words = ["apple", "banana", "cherry", "avocado", "blueberry"]
grouped = defaultdict(list)     # default factory = list()
for word in words:
    grouped[word[0]].append(word)   # no KeyError, creates [] automatically
print(dict(grouped))
# {'a': ['apple', 'avocado'], 'b': ['banana', 'blueberry'], 'c': ['cherry']}

# Count occurrences (manual)
counter_dict = defaultdict(int)     # default = 0
for word in words:
    counter_dict[word[0]] += 1
print(dict(counter_dict))   # {'a': 2, 'b': 2, 'c': 1}

# Nested defaultdict
nested = defaultdict(lambda: defaultdict(int))
nested["fruits"]["apple"] += 1
nested["fruits"]["banana"] += 3
print(nested["fruits"]["apple"])    # 1

# --- Counter ---
# Specialized dict for counting hashable objects

text = "the quick brown fox jumps over the lazy dog"
word_count = Counter(text.split())
print(word_count)
# Counter({'the': 2, 'quick': 1, 'brown': 1, ...})

# Most common items
print(word_count.most_common(3))
# [('the', 2), ('quick', 1), ('brown', 1)]

# Count characters
char_count = Counter("banana")
print(char_count)   # Counter({'a': 3, 'n': 2, 'b': 1})

# Counter arithmetic
c1 = Counter({"a": 3, "b": 1})
c2 = Counter({"a": 1, "b": 2, "c": 5})
print(c1 + c2)  # Counter({'c': 5, 'a': 4, 'b': 3})
print(c1 - c2)  # Counter({'a': 2})  (negatives dropped)
print(c1 & c2)  # Counter({'a': 1, 'b': 1})  (min of each)

# --- OrderedDict ---
# Remembers insertion order (dicts do this too in 3.7+)
# Still useful for move_to_end() and order-sensitive equality

od = OrderedDict([("a", 1), ("b", 2), ("c", 3)])
od.move_to_end("a")         # move to end
od.move_to_end("c", last=False)  # move to front
print(list(od.keys()))      # ['c', 'b', 'a']

Frequently Asked Questions

dict[key] raises a KeyError if the key does not exist. dict.get(key) returns None by default (or a custom default you provide: dict.get(key, default)). Use dict.get() when the key may not be present and you want to handle that gracefully. Use dict[key] when the key must exist and you want a loud failure if it does not.

Python 3.9+ supports the merge operator: merged = d1 | d2 (creates new dict) and d1 |= d2 (updates d1 in place). For older Python: merged = {**d1, **d2} (unpacking) creates a new merged dict, with d2 values winning on conflicts. d1.update(d2) updates d1 in place.

Yes, as of Python 3.7+, dictionaries maintain insertion order as a language guarantee (CPython 3.6+ also maintained order as an implementation detail). When you iterate over a dict, you get keys in the order they were inserted. If you need a dict that also supports order-based operations (like moving keys to front/end), use collections.OrderedDict.

Any hashable (immutable) type: strings, numbers, booleans, tuples (if all elements are hashable), None. Mutable types - lists, dicts, sets - cannot be keys because their hash value would change when modified. If you need a mutable container as a key, convert it to a tuple or frozenset first.