Python Dictionary Complete Guide
Learn all Python dictionary operations - creation, manipulation, built-in methods, comprehension, and advanced operations with practical examples.
Key-Value
Pairs storage
Fast Lookup
O(1) access
Mutable
Can be modified
Unordered
Python 3.7+ ordered
Table of Contents
- Introduction to Dictionaries
- Creating Dictionaries
- Accessing Dictionary Elements
- Modifying Dictionaries
- Dictionary Methods
- Dictionary Methods Reference Table
- Looping Through Dictionaries
- Dictionary Comprehension
- Advanced Dictionary Operations
- OrderedDict
- DefaultDict
- Counter
- Common Pitfalls
- Key Takeaways
1. Introduction to Dictionaries
Dictionaries are unordered (Python 3.7+ maintains insertion order), mutable collections of key-value pairs. They are optimized for retrieving values by key.
Key Properties
- Keys must be unique and immutable (strings, numbers, tuples)
- Values can be any data type (mutable or immutable)
- Mutable — can be changed after creation
- Fast lookups by key — O(1) average time
2. Creating Dictionaries
# Empty dictionary
empty_dict = {}
empty_dict2 = dict()
# Using curly braces
person = {
"name": "Alice",
"age": 30,
"city": "New York"
}
# Using dict() constructor
person2 = dict(name="Bob", age=25, city="Los Angeles")
# From list of tuples
items = [("name", "Charlie"), ("age", 35), ("city", "Chicago")]
person3 = dict(items)
# Using fromkeys() - creates dict with default values
keys = ["name", "age", "city"]
default_dict = dict.fromkeys(keys, "unknown")
print(default_dict) # {'name': 'unknown', 'age': 'unknown', 'city': 'unknown'}
# Dictionary with mixed key types
mixed = {
"name": "David",
1: "one",
(1, 2): "tuple key",
True: "boolean key"
}
# Values can be any type
complex_dict = {
"numbers": [1, 2, 3],
"metadata": {"created": "2024", "version": 1},
"active": True
}
3. Accessing Dictionary Elements
Basic Access
person = {"name": "Alice", "age": 30, "city": "New York"}
# Using square brackets (KeyError if key doesn't exist)
print(person["name"]) # Alice
print(person["age"]) # 30
# KeyError for missing key
# print(person["country"]) # KeyError!
# Using get() (returns None or default if key doesn't exist)
print(person.get("country")) # None
print(person.get("country", "USA")) # USA
print(person.get("city", "Unknown")) # New York
# Using setdefault() - get value or set default if key missing
print(person.setdefault("phone", "123-456-7890")) # 123-456-7890
print(person) # phone is now added to dict
Accessing Nested Elements
nested_dict = {
"user1": {
"name": "Alice",
"address": {
"city": "New York",
"zip": "10001"
}
},
"user2": {
"name": "Bob",
"address": {
"city": "Los Angeles",
"zip": "90210"
}
}
}
print(nested_dict["user1"]["name"]) # Alice
print(nested_dict["user2"]["address"]["city"]) # Los Angeles
4. Modifying Dictionaries
Adding and Updating Elements
person = {"name": "Alice", "age": 30}
# Adding new key-value pair
person["city"] = "New York"
print(person) # {'name': 'Alice', 'age': 30, 'city': 'New York'}
# Updating existing key
person["age"] = 31
print(person) # {'name': 'Alice', 'age': 31, 'city': 'New York'}
# Using update() - merges another dictionary
person.update({"country": "USA", "phone": "123-456-7890"})
print(person)
# Update with keyword arguments
person.update(age=32, city="Boston")
print(person)
# Update with list of tuples
person.update([("zip", "02108"), ("state", "MA")])
print(person)
Removing Elements
person = {"name": "Alice", "age": 30, "city": "New York", "phone": "123-4567"}
# pop() - remove and return value
age = person.pop("age")
print(age) # 30
print(person) # {'name': 'Alice', 'city': 'New York', 'phone': '123-4567'}
# pop() with default (no KeyError)
country = person.pop("country", "Not found")
print(country) # Not found
# popitem() - remove and return (key, value) pair (LIFO in Python 3.7+)
key, value = person.popitem()
print(f"Removed: {key}={value}") # Removed: phone=123-4567
print(person) # {'name': 'Alice', 'city': 'New York'}
# del statement
del person["city"]
print(person) # {'name': 'Alice'}
# clear() - remove all items
person.clear()
print(person) # {}
5. Dictionary Methods
Information Methods
person = {"name": "Alice", "age": 30, "city": "New York", "age": 31} # Duplicate keys overwrite
# Length (number of key-value pairs)
print(len(person)) # 3
# Check if key exists
print("name" in person) # True
print("country" in person) # False
print("name" not in person) # False
# Check if value exists
print("Alice" in person.values()) # True
print(40 in person.values()) # False
# Get all keys
keys = person.keys()
print(keys) # dict_keys(['name', 'age', 'city'])
# Get all values
values = person.values()
print(values) # dict_values(['Alice', 31, 'New York'])
# Get all key-value pairs
items = person.items()
print(items) # dict_items([('name', 'Alice'), ('age', 31), ('city', 'New York')])
Dictionary Views as Dynamic
person = {"name": "Alice", "age": 30}
keys = person.keys()
values = person.values()
print(keys) # dict_keys(['name', 'age'])
print(values) # dict_values(['Alice', 30])
# Views are dynamic - they reflect changes to the dictionary
person["city"] = "New York"
print(keys) # dict_keys(['name', 'age', 'city'])
print(values) # dict_values(['Alice', 30, 'New York'])
# Convert views to lists
key_list = list(person.keys())
value_list = list(person.values())
item_list = list(person.items())
Python Dictionary Methods Complete Reference
Python dictionaries provide built-in methods for access, updates, views, and copying. Here is a concise reference with examples.
Complete Dictionary Methods Reference Table
| Category | Method | Description | Syntax Example | Result |
|---|---|---|---|---|
| Access | get() | Returns value for key, or default if missing | d.get("a", 0) |
Value or default |
| Access | setdefault() | Gets value; if key missing, sets it to default and returns it | d.setdefault("k", []) |
Value (possibly newly set) |
| Views | keys() | Dynamic view of dict keys | d.keys() |
dict_keys(...) |
| Views | values() | Dynamic view of dict values | d.values() |
dict_values(...) |
| Views | items() | Dynamic view of (key, value) pairs | d.items() |
dict_items(...) |
| Modification | clear() | Removes all key-value pairs | d.clear() |
None; dict is {} |
| Modification | pop() | Removes key and returns value; optional default if missing | d.pop("a", None) |
Value removed or default |
| Modification | popitem() | Removes and returns last inserted pair (LIFO, 3.7+) | d.popitem() |
(key, value) |
| Modification | update() | Merges from mapping or iterable of pairs / kwargs | d.update({"b": 2}) |
None; dict updated in place |
| Copy | copy() | Shallow copy of the dictionary | d.copy() |
New dict (shallow) |
| Class | fromkeys() | New dict with keys from iterable, all values equal (default None) |
dict.fromkeys(["a","b"], 0) |
{"a": 0, "b": 0} |
- Use
get()instead of[]when the key might be missing. keys(),values(), anditems()reflect live changes to the dict.popitem()removes items in LIFO order (insertion order) in Python 3.7+.- Avoid
dict.fromkeys(keys, [])—the same list is shared; use a comprehension instead. - For nested dicts,
copy()is shallow; usecopy.deepcopy()for full independence.
6. Looping Through Dictionaries
Basic Looping
person = {"name": "Alice", "age": 30, "city": "New York"}
# Loop through keys
for key in person:
print(f"{key}: {person[key]}")
# Loop through keys explicitly
for key in person.keys():
print(key)
# Loop through values
for value in person.values():
print(value)
# Loop through key-value pairs
for key, value in person.items():
print(f"{key} = {value}")
Advanced Looping Techniques
# Sorted iteration
fruits = {"apple": 5, "banana": 3, "orange": 7, "grape": 2}
for key in sorted(fruits):
print(f"{key}: {fruits[key]}")
# Iterate in reverse
for key in reversed(fruits):
print(f"{key}: {fruits[key]}")
# Iterate with conditional
for key, value in fruits.items():
if value > 4:
print(f"{key} has {value} items")
# Using enumerate with dict
for i, (key, value) in enumerate(fruits.items()):
print(f"{i}. {key}: {value}")
# Using zip with dict
keys = list(fruits.keys())
values = list(fruits.values())
for k, v in zip(keys, values):
print(f"{k}: {v}")
7. Dictionary Comprehension
Dictionary comprehension provides a concise way to create dictionaries.
Basic Syntax
# Create dictionary of squares
squares = {x: x**2 for x in range(1, 6)}
print(squares) # {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
# Create from two lists
names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 35]
people = {name: age for name, age in zip(names, ages)}
print(people) # {'Alice': 25, 'Bob': 30, 'Charlie': 35}
With Conditions
# Filtering
numbers = range(1, 11)
even_squares = {x: x**2 for x in numbers if x % 2 == 0}
print(even_squares) # {2: 4, 4: 16, 6: 36, 8: 64, 10: 100}
# Conditional values
parity = {x: "even" if x % 2 == 0 else "odd" for x in range(1, 6)}
print(parity) # {1: 'odd', 2: 'even', 3: 'odd', 4: 'even', 5: 'odd'}
# Transforming a dictionary
original = {"apple": 5, "banana": 3, "orange": 7}
upscaled = {k: v * 10 for k, v in original.items()}
print(upscaled) # {'apple': 50, 'banana': 30, 'orange': 70}
8. Advanced Dictionary Operations
Merging Dictionaries
# Python 3.5+ using ** operator
dict1 = {"a": 1, "b": 2}
dict2 = {"c": 3, "d": 4}
merged = {**dict1, **dict2}
print(merged) # {'a': 1, 'b': 2, 'c': 3, 'd': 4}
# With overlapping keys (later overwrites earlier)
dict1 = {"a": 1, "b": 2}
dict2 = {"b": 99, "c": 3}
merged = {**dict1, **dict2}
print(merged) # {'a': 1, 'b': 99, 'c': 3}
# Python 3.9+ using | operator
merged = dict1 | dict2
print(merged) # {'a': 1, 'b': 99, 'c': 3}
# Update in-place
dict1.update(dict2)
print(dict1) # {'a': 1, 'b': 99, 'c': 3}
Dictionary Union (Python 3.9+)
# | operator for union
d1 = {"a": 1, "b": 2}
d2 = {"c": 3, "d": 4}
union = d1 | d2
print(union) # {'a': 1, 'b': 2, 'c': 3, 'd': 4}
# |= for in-place update
d1 |= d2
print(d1) # {'a': 1, 'b': 2, 'c': 3, 'd': 4}
Deep Copy for Nested Dictionaries
import copy
original = {
"user": {
"name": "Alice",
"preferences": {"theme": "dark", "language": "en"}
}
}
# Shallow copy
shallow = original.copy()
original["user"]["preferences"]["theme"] = "light"
print(shallow["user"]["preferences"]["theme"]) # light (changed!)
# Deep copy
deep = copy.deepcopy(original)
original["user"]["preferences"]["language"] = "es"
print(deep["user"]["preferences"]["language"]) # en (unchanged!)
9. OrderedDict
While regular dictionaries maintain insertion order from Python 3.7+, OrderedDict provides additional functionality.
from collections import OrderedDict
# Creating OrderedDict
ordered = OrderedDict()
ordered["a"] = 1
ordered["b"] = 2
ordered["c"] = 3
print(ordered) # OrderedDict([('a', 1), ('b', 2), ('c', 3)])
# Move to end/beginning
ordered.move_to_end("b")
print(ordered) # OrderedDict([('a', 1), ('c', 3), ('b', 2)])
ordered.move_to_end("a", last=False)
print(ordered) # OrderedDict([('a', 1), ('c', 3), ('b', 2)])
# Pop from beginning
first = ordered.popitem(last=False)
print(first) # ('a', 1)
print(ordered) # OrderedDict([('c', 3), ('b', 2)])
# Equality considers order in OrderedDict
od1 = OrderedDict([("a", 1), ("b", 2)])
od2 = OrderedDict([("b", 2), ("a", 1)])
print(od1 == od2) # False (different order)
# Regular dict equality ignores order
d1 = {"a": 1, "b": 2}
d2 = {"b": 2, "a": 1}
print(d1 == d2) # True
10. DefaultDict
defaultdict provides a default value for missing keys.
from collections import defaultdict
# Default value as int (0)
counter = defaultdict(int)
counter["apple"] += 1
counter["banana"] += 2
print(counter) # defaultdict(<class 'int'>, {'apple': 1, 'banana': 2})
# Default value as list
grouped = defaultdict(list)
grouped["fruits"].append("apple")
grouped["fruits"].append("banana")
grouped["vegetables"].append("carrot")
print(grouped) # defaultdict(<class 'list'>, {'fruits': ['apple', 'banana'], 'vegetables': ['carrot']})
# Default value as set
unique = defaultdict(set)
unique["colors"].add("red")
unique["colors"].add("blue")
unique["colors"].add("red") # Duplicate ignored
print(unique) # defaultdict(<class 'set'>, {'colors': {'blue', 'red'}})
# Custom default factory
def default_value():
return "N/A"
default_dict = defaultdict(default_value)
print(default_dict["missing"]) # N/A
11. Counter
Counter is a dictionary subclass for counting hashable objects.
from collections import Counter
# Creating counters
word_counter = Counter("hello world")
print(word_counter) # Counter({'l': 3, 'o': 2, ...})
list_counter = Counter(['a', 'b', 'c', 'a', 'b', 'a'])
print(list_counter) # Counter({'a': 3, 'b': 2, 'c': 1})
# Creating from dictionary
c = Counter({'a': 3, 'b': 2, 'c': 1})
# Counting elements in a sentence
sentence = "the quick brown fox jumps over the lazy dog"
word_counts = Counter(sentence.split())
print(word_counts)
# Most common elements
print(word_counter.most_common(3)) # [('l', 3), ('o', 2), ('h', 1)]
# Arithmetic operations
c1 = Counter(a=3, b=2, c=1)
c2 = Counter(a=1, b=2, c=3)
print(c1 + c2) # Counter({'a': 4, 'c': 4, 'b': 4})
print(c1 - c2) # Counter({'a': 2})
print(c1 & c2) # Counter({'b': 2, 'c': 1, 'a': 1}) # Intersection (min)
print(c1 | c2) # Counter({'a': 3, 'c': 3, 'b': 2}) # Union (max)
# Elements (returns iterator)
print(list(c1.elements())) # ['a', 'a', 'a', 'b', 'b', 'c']
# Subtract
c1.subtract(c2)
print(c1) # Counter({'a': 2})
12. Common Pitfalls
# Pitfall 1: Using mutable objects as keys
# invalid = {[1, 2]: "list key"} # TypeError: unhashable type: 'list'
valid = {(1, 2): "tuple key"} # Works - tuples are hashable
# Pitfall 2: Modifying dict while iterating
my_dict = {"a": 1, "b": 2, "c": 3}
# This will raise RuntimeError
# for key in my_dict:
# if key == "b":
# del my_dict[key]
# Instead, iterate over copy
for key in list(my_dict.keys()):
if key == "b":
del my_dict[key]
print(my_dict) # {'a': 1, 'c': 3}
# Pitfall 3: Shallow copy with nested dicts
original = {"user": {"name": "Alice"}}
shallow = original.copy()
original["user"]["name"] = "Bob"
print(shallow["user"]["name"]) # Bob (changed!)
# Use deepcopy for nested structures
import copy
deep = copy.deepcopy(original)
original["user"]["name"] = "Charlie"
print(deep["user"]["name"]) # Bob (unchanged)
# Pitfall 4: Using fromkeys with mutable defaults
# All keys share the same list object!
d = dict.fromkeys(["a", "b", "c"], [])
d["a"].append(1)
print(d) # {'a': [1], 'b': [1], 'c': [1]}
# Correct way
d = {key: [] for key in ["a", "b", "c"]}
d["a"].append(1)
print(d) # {'a': [1], 'b': [], 'c': []}
# Pitfall 5: KeyError vs None
person = {"name": "Alice"}
# print(person["age"]) # KeyError!
print(person.get("age")) # None (safer)
# Pitfall 6: Assumed order (pre-Python 3.7)
# In older Python versions, dict order was not guaranteed
# Use OrderedDict if order matters across all versions
Key Takeaways
- Dictionaries map unique, immutable keys to values; lookups are fast on average
- Use
get(),setdefault(), and membership checks to avoid surprises keys(),values(), anditems()return dynamic views- Merge with
update(),{**a, **b}, or|/|=(3.9+) - Use
copy.deepcopywhen cloning nested structures independently OrderedDict,defaultdict, andCounterextend dict behavior for ordering, defaults, and counting- Never mutate a dict while iterating over it—iterate over a snapshot or build a new dict