The Cartographer's Map Room — 50+ dictionary methods, operations, and patterns. Search, filter by category, and copy code examples instantly. Covers built-in methods, operators, comprehensions, merging, and common patterns.
Literal syntax for creating an empty dictionary or a dictionary with initial key-value pairs. Fastest and most common way.
empty = {}
# {}
person = {'name': 'Alice', 'age': 30}
# {'name': 'Alice', 'age': 30}
# Mixed key types (all must be hashable):
mixed = {1: 'one', 'two': 2, (3, 4): 'tuple key'}
Constructor for creating dictionaries. Accepts keyword arguments, iterables of pairs, or mappings. Useful for dynamic creation.
empty = dict() # {} # From keyword arguments: person = dict(name='Alice', age=30) # {'name': 'Alice', 'age': 30} # From list of tuples: pairs = [('a', 1), ('b', 2)] d = dict(pairs) # {'a': 1, 'b': 2}
Create a dictionary from any iterable of key-value pairs. Common sources include zip(), enumerate(), and list comprehensions.
keys = ['a', 'b', 'c'] values = [1, 2, 3] # From zip: d = dict(zip(keys, values)) # {'a': 1, 'b': 2, 'c': 3} # From enumerate: d = dict(enumerate(['x', 'y', 'z'], start=1)) # {1: 'x', 2: 'y', 3: 'z'}
Create a dictionary with keys from seq and all values set to value (default None). Efficient for initialization.
keys = ['a', 'b', 'c'] # Default values are None: d = dict.fromkeys(keys) # {'a': None, 'b': None, 'c': None} # With a default value: d = dict.fromkeys(keys, 0) # {'a': 0, 'b': 0, 'c': 0} # ⚠️ Mutable default is shared: d = dict.fromkeys(keys, []) d['a'].append(1) # All values become [1]!
The classic idiom for creating a dictionary from two parallel sequences. Stops at the shorter iterable.
keys = ['name', 'age', 'city'] values = ['Alice', 30, 'NYC'] person = dict(zip(keys, values)) # {'name': 'Alice', 'age': 30, 'city': 'NYC'} # Unequal lengths — zip stops at shortest: dict(zip(['a', 'b'], [1, 2, 3])) # {'a': 1, 'b': 2}
Create a dictionary from keyword arguments. Keys must be valid Python identifiers. Clean and readable for simple cases.
config = dict( host='localhost', port=8080, debug=True ) # {'host': 'localhost', 'port': 8080, 'debug': True} # Keys must be valid identifiers: # dict(1='one') # SyntaxError! # Use {'1': 'one'} instead
Bracket notation for dictionary lookup. Raises KeyError if key is not found. Fastest lookup method.
person = {'name': 'Alice', 'age': 30}
name = person['name']
# 'Alice'
# Raises KeyError if missing:
# person['city'] # KeyError: 'city'
# Safe check first:
if 'city' in person:
city = person['city']
Return the value for key if it exists, else return default (None if not specified). Never raises KeyError.
person = {'name': 'Alice'}
age = person.get('age')
# None (default default)
age = person.get('age', 0)
# 0
city = person.get('city', 'Unknown')
# 'Unknown'
# Perfect for nested access:
data.get('user', {}).get('name')
If key exists, return its value. If not, insert key with default value and return default. One-line get-or-create.
counts = {}
# If 'apple' not in counts, set to 0, then return it:
counts.setdefault('apple', 0)
# 0, and counts is now {'apple': 0}
counts['apple'] += 1
# counts → {'apple': 1}
# Grouping pattern:
groups = {}
for item in items:
key = item.category
groups.setdefault(key, []).append(item)
A dictionary subclass that calls a factory function to supply missing values. Eliminates manual key initialization.
from collections import defaultdict # Default value is 0: counts = defaultdict(int) counts['apple'] += 1 # counts → {'apple': 1} (no KeyError!) # Default value is empty list: groups = defaultdict(list) groups['fruits'].append('apple') # Custom factory: def default_user(): return {'name': '', 'active': False} users = defaultdict(default_user)
Set a key to a value. If key exists, overwrites. If not, creates new entry. The fundamental dictionary assignment.
person = {'name': 'Alice'}
# Add new key:
person['age'] = 30
# {'name': 'Alice', 'age': 30}
# Update existing key:
person['age'] = 31
# {'name': 'Alice', 'age': 31}
# Dynamic key:
key = 'city'
person[key] = 'NYC'
Update dictionary with key-value pairs from other. Overwrites existing keys. Accepts dicts, iterables, and keyword args.
person = {'name': 'Alice', 'age': 30}
# Update from another dict:
person.update({'age': 31, 'city': 'NYC'})
# {'name': 'Alice', 'age': 31, 'city': 'NYC'}
# Update from keyword args:
person.update(country='USA', job='Engineer')
# Update from iterable of pairs:
person.update([('hobby', 'coding')])
In-place union operator (Python 3.9+). Equivalent to update() but can be used in expressions. Updates left dict in-place.
person = {'name': 'Alice'}
defaults = {'age': 0, 'city': 'Unknown'}
# In-place merge:
person |= defaults
# person → {'name': 'Alice', 'age': 0, 'city': 'Unknown'}
# Can chain (unlike update):
person |= {'country': 'USA'}
# Works in expressions:
result = (person := person | {'active': True})
Delete a key-value pair. Raises KeyError if key doesn't exist. No return value. Use for unconditional deletion.
person = {'name': 'Alice', 'age': 30, 'city': 'NYC'}
del person['age']
# {'name': 'Alice', 'city': 'NYC'}
# Safe deletion:
if 'age' in person:
del person['age']
# del is a statement, not expression:
# x = del person['age'] # SyntaxError!
Remove key and return its value. If key is missing and default provided, return default. Otherwise raises KeyError.
person = {'name': 'Alice', 'age': 30}
age = person.pop('age')
# age = 30, person → {'name': 'Alice'}
# With default:
city = person.pop('city', 'Unknown')
# city = 'Unknown', no error
# Remove and process:
while person:
key, value = person.popitem()
print(f"{key}: {value}")
Remove and return the last inserted (key, value) pair as a tuple. LIFO order in Python 3.7+. Raises KeyError if empty.
person = {'a': 1, 'b': 2, 'c': 3}
item = person.popitem()
# item → ('c', 3) (LIFO in 3.7+)
# person → {'a': 1, 'b': 2}
# Safe emptying:
while person:
key, value = person.popitem()
print(f"Processing {key}={value}")
# Before 3.7, order was arbitrary
Remove all items from the dictionary. Keeps the same object identity. Useful for resetting state.
cache = {'a': 1, 'b': 2}
id_before = id(cache)
cache.clear()
# cache → {}
id(cache) == id_before # True (same object)
# Equivalent but less explicit:
cache = {} # Creates NEW dict object
# Use clear() when other variables reference the dict
Return the number of key-value pairs. O(1) operation. Works on all built-in collections.
person = {'name': 'Alice', 'age': 30}
len(person) # 2
len({}) # 0
# Truthiness:
if person: # True if len > 0
if not person: # True if empty
Test if a key exists in the dictionary. O(1) average case. Uses hash lookup. The Pythonic way to check membership.
person = {'name': 'Alice', 'age': 30}
'name' in person # True
'salary' in person # False
# Negation:
'city' not in person # True
# Note: checks keys, not values:
'Alice' in person # False (it's a value)
Return a view object of all keys. Dynamic — reflects dict changes. Iterable and supports membership testing.
person = {'name': 'Alice', 'age': 30}
keys = person.keys()
# dict_keys(['name', 'age'])
# Membership test (O(1)):
'name' in keys # True
# Convert to list if needed:
list(keys) # ['name', 'age']
# Dynamic view:
person['city'] = 'NYC'
# keys now includes 'city'
Return a view object of all values. Dynamic — reflects dict changes. Membership test is O(n) since values aren't hashed.
person = {'name': 'Alice', 'age': 30}
values = person.values()
# dict_values(['Alice', 30])
# Membership is O(n):
'Alice' in values # True
# Convert to list:
list(values) # ['Alice', 30]
# Dynamic:
person['city'] = 'NYC'
# values now includes 'NYC'
Return a view of (key, value) tuples. The standard way to iterate over both keys and values simultaneously.
person = {'name': 'Alice', 'age': 30}
items = person.items()
# dict_items([('name', 'Alice'), ('age', 30)])
# Iterate:
for key, value in person.items():
print(f"{key}: {value}")
# Convert to list of tuples:
list(person.items())
# [('name', 'Alice'), ('age', 30)]
Return a shallow copy of the dictionary. New dict object with same key-value references. Fast and Pythonic.
original = {'a': 1, 'b': [2, 3]}
copied = original.copy()
# Different objects:
copied is original # False
copied == original # True
# But nested objects are shared:
copied['b'].append(4)
# original['b'] → [2, 3, 4]
# Equivalent: dict(original), original[:]
Compare two dictionaries for equality. True if same key-value pairs regardless of insertion order (in 3.7+ order also matches).
a = {'x': 1, 'y': 2}
b = {'y': 2, 'x': 1}
a == b # True (same pairs)
a is b # False (different objects)
# Nested dicts compared recursively:
c = {'a': {'b': 1}}
d = {'a': {'b': 1}}
c == d # True
Union operator (Python 3.9+). Returns a new dictionary with keys from both. Right side wins on conflicts. Originals unchanged.
defaults = {'theme': 'dark', 'font': '16px'}
user = {'font': '14px', 'lang': 'en'}
merged = defaults | user
# {'theme': 'dark', 'font': '14px', 'lang': 'en'}
# defaults and user unchanged
# Can chain:
result = a | b | c | {'extra': True}
In-place union operator (Python 3.9+). Updates d1 with keys from d2. Right side wins conflicts. Returns None.
config = {'host': 'localhost', 'port': 8080}
overrides = {'port': 3000, 'debug': True}
config |= overrides
# config → {'host': 'localhost', 'port': 3000, 'debug': True}
# Equivalent to update() but operator syntax:
# config.update(overrides)
Dictionary unpacking (Python 3.5+). Unpacks key-value pairs into a new dict. Rightmost values win on conflicts.
base = {'a': 1, 'b': 2}
extra = {'b': 3, 'c': 4}
merged = {**base, **extra}
# {'a': 1, 'b': 3, 'c': 4}
# Add extra keys inline:
result = {**base, 'd': 5, **extra}
# Merge multiple dicts:
all_three = {**a, **b, **c}
Create a deep copy — recursively copies all nested objects. No shared references. Use for nested mutable structures.
import copy data = { 'users': [{'name': 'Alice'}], 'settings': {'theme': 'dark'} } copied = copy.deepcopy(data) copied['users'][0]['name'] = 'Bob' # copied['users'][0]['name'] → 'Bob' # data['users'][0]['name'] → 'Alice' (unaffected!)
Group multiple dicts into a single view without copying. Lookups search left-to-right. Mutations affect the first dict.
from collections import ChainMap defaults = {'theme': 'dark', 'lang': 'en'} user = {'lang': 'fr'} config = ChainMap(user, defaults) config['lang'] # 'fr' (from user) config['theme'] # 'dark' (from defaults) # No copying — memory efficient: len(config) # 2 (only user keys count)
Iterate over dictionary keys. In Python 3.7+, keys are yielded in insertion order. The most common iteration pattern.
person = {'name': 'Alice', 'age': 30, 'city': 'NYC'}
for key in person:
print(key)
# name, age, city (insertion order)
# Equivalent to:
for key in person.keys():
print(key)
Iterate over key-value pairs simultaneously. The standard Pythonic pattern for accessing both during iteration.
scores = {'Alice': 95, 'Bob': 87, 'Carol': 92}
for name, score in scores.items():
print(f"{name}: {score}")
# Build a new dict from items:
capped = {k: min(v, 90) for k, v in scores.items()}
keys(), values(), and items() return dynamic views. They reflect dict changes in real-time and support set-like operations.
d = {'a': 1, 'b': 2, 'c': 3}
keys = d.keys()
# Dynamic — reflects changes:
d['d'] = 4
len(keys) # 4
# Set operations on keys:
d1 = {'a': 1, 'b': 2}
d2 = {'b': 3, 'c': 4}
d1.keys() & d2.keys() # {'b'} (intersection)
d1.keys() | d2.keys() # {'a', 'b', 'c'} (union)
iter(d) returns an iterator over keys. reversed(d) yields keys in reverse insertion order (Python 3.8+).
d = {'a': 1, 'b': 2, 'c': 3}
# Iterator over keys:
it = iter(d)
next(it) # 'a'
next(it) # 'b'
# Reverse iteration (3.8+):
for key in reversed(d):
print(key)
# c, b, a
# Convert to list:
list(reversed(d)) # ['c', 'b', 'a']
Basic dictionary comprehension. Transform iterables into dictionaries. Faster and more readable than loops.
# Squares mapping: {k: k**2 for k in range(5)} # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16} # From two lists: keys = ['a', 'b', 'c'] values = [1, 2, 3] {k: v for k, v in zip(keys, values)} # {'a': 1, 'b': 2, 'c': 3}
Filtered dictionary comprehension. Only include items where the condition is True. Powerful one-line filtering.
scores = {'Alice': 95, 'Bob': 72, 'Carol': 88, 'Dave': 91}
# Only passing scores:
{k: v for k, v in scores.items() if v >= 80}
# {'Alice': 95, 'Carol': 88, 'Dave': 91}
# Filter by key:
{k: v for k, v in scores.items() if k.startswith('A')}
Conditional expression inside comprehension. Transform values based on conditions while keeping all keys.
scores = {'Alice': 95, 'Bob': 72, 'Carol': 88}
# Pass/fail labels:
{k: ('pass' if v >= 80 else 'fail') for k, v in scores.items()}
# {'Alice': 'pass', 'Bob': 'fail', 'Carol': 'pass'}
# Clamp values:
{k: (min(v, 100) if v > 0 else 0) for k, v in scores.items()}
Nested loops inside comprehensions for flattening or building multi-level structures. Readability decreases with depth.
# Flatten nested data: data = {'a': [1, 2], 'b': [3, 4]} {k: v for k, lst in data.items() for v in lst} # Not valid — duplicate keys! # Group by first letter: words = ['apple', 'apricot', 'banana', 'cherry'] {w[0]: [x for x in words if x.startswith(w[0])] for w in words} # {'a': ['apple', 'apricot'], 'b': ['banana'], 'c': ['cherry']}
Use dict() constructor with zip() or enumerate() for clean one-liners when creating dicts from sequences.
keys = ['a', 'b', 'c'] values = [1, 2, 3] # Classic idiom: dict(zip(keys, values)) # {'a': 1, 'b': 2, 'c': 3} # Enumerate for index mapping: dict(enumerate(['x', 'y', 'z'])) # {0: 'x', 1: 'y', 2: 'z'} # With start: dict(enumerate(['a', 'b'], start=1)) # {1: 'a', 2: 'b'}
Group items by a key without imports. setdefault creates the list on first access, then appends each item.
words = ['apple', 'apricot', 'banana', 'blueberry', 'cherry'] groups = {} for word in words: first = word[0] groups.setdefault(first, []).append(word) # {'a': ['apple', 'apricot'], # 'b': ['banana', 'blueberry'], # 'c': ['cherry']} # Equivalent with defaultdict: from collections import defaultdict groups = defaultdict(list) for word in words: groups[word[0]].append(word)
Swap keys and values. Values must be unique and hashable. For non-unique values, group into lists.
original = {'a': 1, 'b': 2, 'c': 3}
# Simple inversion (values unique):
inverted = {v: k for k, v in original.items()}
# {1: 'a', 2: 'b', 3: 'c'}
# Handle duplicate values:
original = {'a': 1, 'b': 1, 'c': 2}
inverted = {}
for k, v in original.items():
inverted.setdefault(v, []).append(k)
# {1: ['a', 'b'], 2: ['c']}
A specialized dict for counting hashable objects. Returns 0 for missing keys instead of KeyError. Powerful frequency analysis.
from collections import Counter words = ['apple', 'banana', 'apple', 'cherry', 'apple'] counts = Counter(words) # Counter({'apple': 3, 'banana': 1, 'cherry': 1}) counts['apple'] # 3 counts['grape'] # 0 (no error!) # Most common: counts.most_common(2) # [('apple', 3), ('banana', 1)]
Manual frequency counting without imports. Useful when Counter is overkill or you need custom logic.
items = ['a', 'b', 'a', 'c', 'a', 'b'] # Using get(): counts = {} for item in items: counts[item] = counts.get(item, 0) + 1 # {'a': 3, 'b': 2, 'c': 1} # Using setdefault: counts = {} for item in items: counts.setdefault(item, 0) counts[item] += 1
Merge dictionaries with custom conflict resolution using a loop or collections.Counter for numeric values.
d1 = {'a': 10, 'b': 20}
d2 = {'b': 30, 'c': 40}
# Sum values on conflict:
merged = {k: d1.get(k, 0) + d2.get(k, 0) for k in d1.keys() | d2.keys()}
# {'a': 10, 'b': 50, 'c': 40}
# Max on conflict:
merged = {k: max(d1.get(k, 0), d2.get(k, 0)) for k in d1.keys() | d2.keys()}
Find the key with the maximum value. d.get is passed as the key function. Elegant one-liner for rankings.
scores = {'Alice': 95, 'Bob': 87, 'Carol': 92}
# Key with max value:
max(scores, key=scores.get)
# 'Alice'
# Key with min value:
min(scores, key=scores.get)
# 'Bob'
# Get the actual max value:
max(scores.values())
# 95
Sort a dictionary by its values (or keys) and return a new dict. Python 3.7+ preserves insertion order.
scores = {'Alice': 95, 'Bob': 87, 'Carol': 92}
# Sort by value ascending:
dict(sorted(scores.items(), key=lambda x: x[1]))
# {'Bob': 87, 'Carol': 92, 'Alice': 95}
# Sort by value descending:
dict(sorted(scores.items(), key=lambda x: x[1], reverse=True))
# Sort by key:
dict(sorted(scores.items()))
Choosing the right access method prevents KeyErrors and keeps your code clean.
| Feature | d[key] | d.get(key, default) | d.setdefault(key, default) |
|---|---|---|---|
| Returns on miss | Raises KeyError | default (or None) | default (and inserts it) |
| Mutates dict | No | No | Yes (if key missing) |
| Default value | N/A | Configurable | Configurable |
| Raises error | Yes | No | No |
| Use case | You know key exists | Safe read with fallback | Get-or-create pattern |
Python offers multiple ways to merge dictionaries. Choose based on whether you need a new dict or in-place mutation.
| Feature | d.update(other) | d1 | d2 | d1 |= d2 | {**a, **b} |
|---|---|---|---|---|
| Returns | None | New dict | None | New dict |
| Mutates a | Yes | No | Yes | No |
| Mutates b | No | No | No | No |
| Works on | Any mapping | Dicts only | Dicts only | Any mapping |
| Python version | All | 3.9+ | 3.9+ | 3.5+ |
Three ways to remove items. Choose based on whether you need the value back, a default fallback, or LIFO removal.
| Feature | d.pop(key, default) | del d[key] | d.popitem() |
|---|---|---|---|
| Returns | Value (or default) | Nothing | (key, value) tuple |
| Default on miss | Yes (if provided) | No (raises KeyError) | No (raises KeyError) |
| Raises | KeyError (no default) | KeyError | KeyError (if empty) |
| Order | Specific key | Specific key | LIFO (last inserted, 3.7+) |
Big-O complexity for common dictionary operations. Assumes average case with good hash distribution.
| Operation | Average Case | Worst Case | Notes |
|---|---|---|---|
Get d[key] | O(1) | O(n) | Hash collision chain |
Set d[key] = val | O(1) | O(n) | May trigger resize |
Delete del d[key] | O(1) | O(n) | Hash collision chain |
| Iteration (keys/values/items) | O(n) | O(n) | Over all n items |
d.keys() / d.values() | O(1) | O(1) | Returns view, not copy |
d.items() | O(1) | O(1) | Returns view, not copy |
len(d) | O(1) | O(1) | Stored internally |
key in d | O(1) | O(n) | Hash lookup |
| Merge (| or update) | O(m) | O(m*n) | m = size of other dict |
d.copy() | O(n) | O(n) | Shallow copy |
n = number of items in dictionary. Worst case occurs with malicious hash collisions or extremely poor hash distribution.
Common pitfalls and powerful techniques every Python developer should know when working with dictionaries.
Never use {} as a default argument. It creates one dict at function definition time, shared across all calls. Use None instead and assign d = d or {} inside.
Never add or delete keys while iterating over a dict. It raises RuntimeError. Instead, iterate over list(d.items()) or build a new dict with a comprehension.
Dictionary keys must be hashable (immutable): strings, numbers, tuples of hashables. Lists and dicts cannot be keys. Use tuple(my_list) if you need a list as a key.
keys(), values(), and items() return live views. They reflect dict changes in real-time. If you need a static snapshot, convert to list().
Chain .get() with defaults for safe nested access: data.get('user', {}).get('profile', {}).get('name'). Returns None at any missing level without errors.
Use collections.Counter for counting — it's optimized, readable, and handles edge cases. For simple cases without imports, counts[x] = counts.get(x, 0) + 1 is fine.