Python Dictionaries
Learn Python dictionaries: create, access, update, delete, iterate, and use comprehensions with clear examples and common gotchas.
A Python dictionary is a mutable, ordered collection of key-value pairs. Unlike lists or tuples (which use integer positions), a dictionary lets you label each value with a meaningful key — making lookups fast and code more readable. Dictionaries are one of Python's most-used built-in types and are the backbone of JSON handling, configuration management, word counting, and much more.
This chapter covers everything you need to know to use dictionaries confidently: creation, access, modification, deletion, iteration, comprehensions, and common gotchas.
Related chapters: Dictionary Methods | Nested Dictionaries | Loop Dictionaries | Copy Dictionaries | Python Comprehensions
What is a Dictionary?
A dictionary maps unique, hashable keys to values of any type. The key rule: keys must be immutable — strings, numbers, and tuples of immutables all qualify; lists and other dictionaries do not.
# string keys → integer values
inventory = {'apple': 10, 'banana': 5, 'orange': 8}
# mixed value types are fine
person = {'name': 'Alice', 'age': 30, 'active': True}
# tuple keys work because tuples are immutable
grid = {(0, 0): 'origin', (1, 0): 'east', (0, 1): 'north'}Since Python 3.7, dictionaries maintain insertion order — iterating a dictionary always yields keys in the order they were added.
Creating a Dictionary
Curly-brace literal
The most common way: separate key-value pairs with commas, use a colon between each key and its value, and wrap the whole thing in {}.
config = {'host': 'localhost', 'port': 5432, 'debug': True}
print(config)
# {'host': 'localhost', 'port': 5432, 'debug': True}An empty dictionary is just {}.
empty = {}
print(type(empty)) # <class 'dict'>dict() constructor
Pass keyword arguments to dict() when your keys are valid Python identifiers:
config = dict(host='localhost', port=5432, debug=True)
print(config)
# {'host': 'localhost', 'port': 5432, 'debug': True}You can also pass an iterable of two-element sequences:
pairs = [('x', 10), ('y', 20)]
point = dict(pairs)
print(point) # {'x': 10, 'y': 20}dict.fromkeys()
Create a dictionary from a list of keys, all sharing the same initial value:
defaults = dict.fromkeys(['timeout', 'retries', 'verbose'], 0)
print(defaults)
# {'timeout': 0, 'retries': 0, 'verbose': 0}Gotcha: if the default value is a mutable object (like a list), all keys will share the same object. Use a dictionary comprehension instead (shown below).
Dictionary comprehension
A concise way to build a dictionary from any iterable:
squares = {x: x ** 2 for x in range(1, 6)}
print(squares)
# {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}See Python Comprehensions for the full syntax.
Accessing Values
Square-bracket lookup
inventory = {'apple': 10, 'banana': 5, 'orange': 8}
print(inventory['apple']) # 10If the key does not exist, Python raises a KeyError:
try:
print(inventory['grape'])
except KeyError as e:
print(f'KeyError: {e}') # KeyError: 'grape'get() — safe lookup with a default
dict.get(key, default) returns the value if the key exists, or default (which is None if omitted) otherwise — no exception raised.
print(inventory.get('grape')) # None
print(inventory.get('grape', 0)) # 0
print(inventory.get('apple', 0)) # 10Use .get() any time you are not sure whether a key exists. Use bracket lookup when the key must be there — a missing key is then a genuine bug that should surface as an error.
Checking whether a key exists
print('apple' in inventory) # True
print('grape' in inventory) # False
print('grape' not in inventory) # Truein tests keys, not values. It runs in O(1) average time — constant, no matter how large the dictionary.
Adding and Updating Items
Add a new key-value pair
Assign to a key that does not yet exist:
inventory['grape'] = 12
print(inventory)
# {'apple': 10, 'banana': 5, 'orange': 8, 'grape': 12}Update an existing value
Assign to a key that already exists — the old value is replaced:
inventory['apple'] = 15
print(inventory['apple']) # 15update() — bulk add or overwrite
Pass another dictionary or an iterable of key-value pairs:
inventory.update({'banana': 7, 'mango': 3})
print(inventory)
# {'apple': 15, 'banana': 7, 'orange': 8, 'grape': 12, 'mango': 3}Merge with | (Python 3.9+)
The | operator creates a new merged dictionary. If a key appears in both, the right-hand side wins:
a = {'x': 1, 'y': 2}
b = {'y': 99, 'z': 3}
merged = a | b
print(merged) # {'x': 1, 'y': 99, 'z': 3}Use |= to merge b into a in-place — same as a.update(b) but more readable.
setdefault() — insert only when missing
dict.setdefault(key, default) inserts the key with the default value if it is absent, then always returns the value for that key. This is more efficient than the pattern if key not in d: d[key] = default.
scores = {'Alice': 95}
scores.setdefault('Bob', 0) # Bob absent → inserts 0, returns 0
scores.setdefault('Alice', 0) # Alice present → does nothing, returns 95
print(scores) # {'Alice': 95, 'Bob': 0}A classic use case is grouping items:
words = ['cat', 'car', 'bus', 'can', 'bat']
groups = {}
for word in words:
groups.setdefault(word[0], []).append(word)
print(groups)
# {'c': ['cat', 'car', 'can'], 'b': ['bus', 'bat']}Removing Items
del — remove by key
inventory = {'apple': 15, 'banana': 7, 'orange': 8, 'grape': 12}
del inventory['orange']
print(inventory)
# {'apple': 15, 'banana': 7, 'grape': 12}del raises KeyError if the key is missing. To avoid this, check with in first, or use pop() with a default.
pop() — remove and return the value
removed = inventory.pop('grape')
print(removed) # 12
print(inventory) # {'apple': 15, 'banana': 7}Supply a second argument to avoid KeyError when the key might be absent:
val = inventory.pop('pear', 'not found')
print(val) # not foundpopitem() — remove the last inserted item (Python 3.7+)
d = {'a': 1, 'b': 2, 'c': 3}
last = d.popitem()
print(last) # ('c', 3)
print(d) # {'a': 1, 'b': 2}clear() — remove everything
d = {'a': 1, 'b': 2}
d.clear()
print(d) # {}Iterating Over a Dictionary
Three views let you iterate over different parts of a dictionary. All three are live views — they reflect changes to the dictionary without needing to be recreated.
Keys (default iteration)
Iterating directly over a dictionary yields its keys:
person = {'name': 'Alice', 'age': 30, 'city': 'Paris'}
for key in person:
print(key)
# name
# age
# cityperson.keys() returns the same keys as an explicit dict_keys view object.
Values
for val in person.values():
print(val)
# Alice
# 30
# ParisKey-value pairs
for key, val in person.items():
print(f'{key}: {val}')
# name: Alice
# age: 30
# city: Paris.items() is the most common choice when you need both key and value inside the loop. See Loop Dictionaries for advanced patterns.
Views are live
d = {'a': 1}
keys_view = d.keys()
d['b'] = 2
print(keys_view) # dict_keys(['a', 'b']) ← reflects the new keyDictionary Comprehensions
Dictionary comprehensions let you build or transform dictionaries in a single readable expression.
Filter by value
inventory = {'apple': 15, 'banana': 7, 'orange': 8, 'grape': 12, 'mango': 3}
popular = {k: v for k, v in inventory.items() if v >= 8}
print(popular)
# {'apple': 15, 'orange': 8, 'grape': 12}Invert a dictionary (swap keys and values)
original = {'a': 1, 'b': 2, 'c': 3}
swapped = {v: k for k, v in original.items()}
print(swapped) # {1: 'a', 2: 'b', 3: 'c'}This only works safely when all values are unique.
Word frequency counter
sentence = 'the quick brown fox jumps over the lazy dog'
freq = {}
for word in sentence.split():
freq[word] = freq.get(word, 0) + 1
print(freq)
# {'the': 2, 'quick': 1, 'brown': 1, 'fox': 1, ...}Useful Built-in Functions
| Operation | Example | Result |
|---|---|---|
| Length | len(inventory) | number of key-value pairs |
| Copy (shallow) | inventory.copy() | new dict, same references |
| Convert to list of keys | list(inventory) | ['apple', 'banana', ...] |
| Sorted keys | sorted(inventory) | ['apple', 'banana', ...] alphabetically |
| All keys truthy? | all(inventory.values()) | True / False |
| Any key truthy? | any(inventory.values()) | True / False |
For a full reference of every dictionary method (copy, fromkeys, update, setdefault, popitem, and more), see Python Dictionary Methods.
Common Gotchas
1. KeyError on missing key — always use .get() or in when a key might not exist.
2. Unhashable key types — lists cannot be dictionary keys because they are mutable. Use a tuple instead:
# This raises TypeError: unhashable type: 'list'
# bad = {[1, 2]: 'value'}
# Use a tuple:
coords = {(1, 2): 'A', (3, 4): 'B'}
print(coords[(1, 2)]) # A3. Mutating while iterating — adding or removing keys while looping over a dictionary raises RuntimeError. Take a snapshot first:
d = {'a': 1, 'b': 2, 'c': 3}
for key in list(d): # list() copies the keys
if d[key] < 2:
del d[key]
print(d) # {'b': 2, 'c': 3}4. Shallow copy vs. deep copy — dict.copy() and {**d} both do a shallow copy. Nested mutable values (lists, dicts) are still shared. Use copy.deepcopy() when you need fully independent copies. See Copy Dictionaries.
Dictionaries vs. Other Collection Types
| Feature | dict | list | tuple | set |
|---|---|---|---|---|
| Ordered | Yes (3.7+) | Yes | Yes | No |
| Mutable | Yes | Yes | No | Yes |
| Indexed by | Key | Integer | Integer | — |
| Duplicates | Keys: no; Values: yes | Yes | Yes | No |
| Main use | Key-value mapping | Ordered sequence | Immutable sequence | Uniqueness / set ops |
When you need to look something up by a meaningful label rather than a position, a dictionary is almost always the right choice. For ordered unique values, consider a set. For a simple ordered sequence, use a list.