user = {"name": "Alice", "age": 30, "role": "developer"}
print(user["name"]) # Alice
That’s a Python dictionary. Key-value pairs wrapped in curly braces, and you’re done.
If you’ve been programming for more than five minutes, you’ve probably used a dictionary without realizing it. They’re everywhere because they solve a fundamental problem: computers are great at remembering things, but only if you label them properly.
Creating Python dictionaries from scratch
The basic syntax gives you three ways to create a dict. Pick whichever feels right for the situation.
# Method 1: Literal notation
person = {"first": "John", "last": "Doe", "age": 28}
# Method 2: dict() constructor
person = dict(first="John", last="Doe", age=28)
# Method 3: Empty dict you'll populate later
person = {}
person["first"] = "John"
person["last"] = "Doe"
person["age"] = 28
All three produce identical results. The literal notation (method 1) is faster and more common in production code. The constructor (method 2) looks cleaner when you’re passing values as function arguments. The empty dict approach (method 3) makes sense when you’re building data iteratively.
Keys can be strings, numbers, or tuples (anything immutable, really). Values can be absolutely anything.
mixed = {
"string_key": "value",
42: "numeric key",
(1, 2): "tuple key",
"nested": {"another": "dict"}
}
Accessing dictionary values in Python
You’ve got two options here, and they behave differently when keys don’t exist.
config = {"debug": True, "port": 8080}
# Square bracket notation
print(config["debug"]) # True
print(config["missing"]) # KeyError: 'missing'
# get() method
print(config.get("debug")) # True
print(config.get("missing")) # None
print(config.get("missing", "default")) # 'default'
The square bracket approach crashes hard when the key doesn’t exist. Sometimes that’s what you want (fail fast). The get() method returns None by default or whatever fallback value you specify. I use get() constantly in production because it prevents defensive coding nonsense.
Modifying dictionary data in Python
Dictionaries are mutable, which means you can change them after creation. This is either convenient or dangerous depending on your perspective.
settings = {"theme": "dark", "lang": "en"}
# Add new key-value pair
settings["notifications"] = True
# Update existing value
settings["theme"] = "light"
# Update multiple values
settings.update({"lang": "es", "timezone": "UTC"})
print(settings)
# {'theme': 'light', 'lang': 'es', 'notifications': True, 'timezone': 'UTC'}
The update() method merges another dictionary into the existing one. Any overlapping keys get overwritten. Any new keys get added.
Removing items from Python dictionaries
Four different ways to delete things, each with different behavior.
data = {"a": 1, "b": 2, "c": 3}
# pop() removes and returns the value
value = data.pop("a") # Returns 1
value = data.pop("missing", "not found") # Returns "not found"
# del keyword removes by key
del data["b"]
# popitem() removes and returns the last item (Python 3.7+)
item = data.popitem() # Returns ('c', 3)
# clear() nukes everything
data.clear() # {}
I almost always use pop() because it handles missing keys gracefully with a default value. The del keyword is fine for quick scripts. Avoid popitem() unless you specifically need LIFO behavior.
Looping through Python dictionaries
Dictionaries give you three ways to iterate, depending on whether you need keys, values, or both.
scores = {"alice": 95, "bob": 87, "charlie": 92}
# Iterate over keys (default behavior)
for name in scores:
print(name)
# Iterate over values
for score in scores.values():
print(score)
# Iterate over key-value pairs
for name, score in scores.items():
print(f"{name} scored {score}")
The items() method is what you want 90% of the time. It unpacks each key-value pair into separate variables, making your code way more readable than scores[name] everywhere.
Dictionary comprehensions in Python
List comprehensions are great. Dictionary comprehensions are the same idea but for dicts.
# Create dict from sequence
numbers = [1, 2, 3, 4, 5]
squares = {n: n**2 for n in numbers}
# {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
# Filter while building
evens = {n: n**2 for n in numbers if n % 2 == 0}
# {2: 4, 4: 16}
# Transform existing dict
prices = {"apple": 1.2, "banana": 0.5, "orange": 0.8}
rounded = {k: round(v) for k, v in prices.items()}
# {'apple': 1, 'banana': 0, 'orange': 1}
Comprehensions run faster than loops because they’re optimized at the bytecode level. They also force you to think declaratively, which usually results in cleaner code.
Checking if keys exist in Python dictionaries
Three approaches, each with different performance characteristics.
user = {"name": "Alice", "email": "[email protected]"}
# in operator (fastest)
if "name" in user:
print("Name exists")
# get() with default
email = user.get("email", "[email protected]")
# keys() method (unnecessary, avoid)
if "name" in user.keys(): # Don't do this
print("Slower than necessary")
The in operator checks membership directly on the hash table. It’s fast and readable. Using user.keys() adds an extra method call for zero benefit.
Common Python dictionary methods you’ll actually use
data = {"x": 10, "y": 20}
# Get all keys
keys = data.keys() # dict_keys(['x', 'y'])
# Get all values
values = data.values() # dict_values([10, 20])
# Get all items
items = data.items() # dict_items([('x', 10), ('y', 20)])
# setdefault() adds key only if missing
data.setdefault("z", 30) # Adds z: 30
data.setdefault("x", 99) # Leaves x: 10 unchanged
The setdefault() method is weirdly useful for building nested structures without checking existence every time.
Merging dictionaries in Python
Python 3.9 added the merge operator, which is cleaner than anything that came before.
defaults = {"color": "blue", "size": "medium"}
custom = {"size": "large", "material": "cotton"}
# Merge operator (Python 3.9+)
final = defaults | custom
# {'color': 'blue', 'size': 'large', 'material': 'cotton'}
# Old way (still works)
final = {**defaults, **custom}
# In-place update
defaults |= custom # Modifies defaults directly
Values from the right side override the left. Simple as that.
Dictionaries are fast because they use hash tables underneath. Lookup time is O(1) on average, meaning it takes the same time whether you have 10 items or 10 million. That’s why you see them everywhere in Python code, from argument parsing to caching to configuration management.



