Python Dictionaries: The Complete Guide

Python Dictionaries: The Complete Guide
If you ask experienced Python developers which built-in data structure they use most, the dictionary almost always tops the list. It's fast, flexible, and built into the core of how Python itself works — from tracking variable scopes to powering class attribute lookups. Whether you're processing JSON, building configuration systems, or just mapping one thing to another, dictionaries are the tool you'll reach for again and again.
What Is a Python Dictionary?
A dictionary is a collection of key-value pairs. Each key acts as a unique identifier, and you use it to store or retrieve an associated value. Here's a simple example:
server_config = {
"host": "localhost",
"port": 8080,
"debug": True,
"timeout": 30,
}
print(server_config["host"]) # localhost
print(server_config["port"]) # 8080Dictionaries are central to Python internals too. The built-in globals() function returns a dictionary of all names defined in the current global scope. When you define a class, Python stores its instance attributes in a dictionary accessible via .__dict__:
class Car:
def init(self, make, model):
self.make = make
self.model = model
my_car = Car("Toyota", "Corolla")
print(my_car.__dict__)
# {'make': 'Toyota', 'model': 'Corolla'}Key Properties
Python dictionaries have several characteristics worth knowing upfront:
Mutable — you can add, modify, and remove entries after creation
Dynamic — they grow and shrink automatically as you add or remove data
Ordered — since Python 3.7, insertion order is guaranteed and preserved
Fast — implemented as hash tables, so lookups are extremely efficient regardless of dictionary size
Rules for Keys and Values
Not everything can be a dictionary key. Keys must be hashable — meaning their value must remain constant throughout their lifetime. In practice, this means strings, numbers, and tuples of immutable items all work as keys, while lists, sets, and other dictionaries do not.
# These all work as keys
valid = {
"name": "Alice",
42: "answer",
(1, 2): "a tuple key",
}
# This raises a TypeError
broken = {[1, 2, 3]: "list as key"}
# TypeError: unhashable type: 'list'Keys must also be unique within a dictionary. If you assign a value to a key that already exists, the old value is replaced — no error is raised, and no duplicate entry is created.
Dictionary values, on the other hand, have no restrictions at all. They can be any Python object — numbers, strings, lists, functions, other dictionaries, or custom class instances — and duplicates are perfectly fine.
Creating Dictionaries
There are three main ways to create a dictionary in Python.
1. Dictionary Literals
The most common approach uses curly braces with colon-separated key-value pairs:
person = {
"first_name": "Sarah",
"last_name": "Mitchell"
"age": 29,
"city": "Edinburgh",
}
For an empty dictionary, use an empty pair of curly braces:
data = {}
Note: Don't confuse this with sets. A set also uses curly braces ({1, 2, 3}), but it contains individual values rather than pairs. To create an empty set, you must use set() — empty curly braces always produce an empty dictionary.
2. The dict() Constructor
The dict() function gives you more flexibility in how you build a dictionary. You can call it in several ways:
From keyword arguments (keys must be valid Python identifiers):
person = dict(first_name="Sarah", last_name="Mitchell", age=29)From a list of tuples:
person = dict([("first_name", "Sarah"), ("last_name", "Mitchell"), ("age", 29)]From two parallel lists using zip():
keys = ["first_name", "last_name", "age"]
values = ["Sarah", "Mitchell", 29]
person = dict(zip(keys, values))This zip() pattern is handy when your keys and values arrive as separate sequences — it pairs them up in order and hands them to dict().
3. The .fromkeys() Class Method
When you need to create a dictionary with a set of predefined keys but don't have the values yet, .fromkeys() is useful:
products = dict.fromkeys(["apple", "banana", "mango", "grape"], 0)
print(products)
# {'apple': 0, 'banana': 0, 'mango': 0, 'grape': 0}
This creates a dictionary where every key maps to the same default value (0 here, or None if you don't specify one). It's a concise way to initialise a tracking or counting structure.
Accessing Values
To retrieve a value, use the key in square brackets:
print(person["first_name"]) # SarahIf you try to access a key that doesn't exist, Python raises a KeyError:
print(person["middle_name"])# KeyError: 'middle_name'
To avoid this, use the .get() method instead. It returns None by default if the key is missing, or a custom value you supply:
print(person.get("middle_name")) # None
print(person.get("middle_name", "N/A")) # N/A
For nested dictionaries or mixed structures, chain the keys (and indices where needed):
employee = {
"name": "David",
"skills": ["Python", "SQL", "Docker"],
"address": {"city": "London", "postcode": "EC1A"},
}
print(employee["skills"][1]) # SQL
print(employee["address"]["city"]) # LondonBuilding Dictionaries Incrementally
You don't always have all your data available up front. Dictionaries can be populated on the fly in several ways.
Manual Assignment
Start with an empty dictionary and add keys one at a time:
stats = {}
stats["total_users"] = 1500
stats["active_today"] = 312
stats["new_signups"] = 47Using a Loop
When constructing a dictionary from a sequence, a for loop works well:
cubes = {}
for n in range(1, 8):
cubes[n] = n ** 3
print(cubes)
# {1: 1, 2: 8, 3: 27, 4: 64, 5: 125, 6: 216, 7: 343}Dictionary Comprehensions
For a more concise version of the above, use a dictionary comprehension:
cubes = {n: n ** 3 for n in range(1, 8)}You can also filter with a condition:
even_squares = {n: n ** 2 for n in range(1, 11) if n % 2 == 0}
# {2: 4, 4: 16, 6: 36, 8: 64, 10: 100}Comprehensions are clean, readable, and generally faster than the loop equivalent.
Dictionary Methods
Python's dict type comes with a rich set of methods. Here's a tour of the most useful ones.
Viewing Data
.keys() returns a view of all keys:
inventory = {"apple": 50, "banana": 30, "mango": 20}
print(inventory.keys())
# dict_keys(['apple', 'banana', 'mango']).values() returns a view of all values:
print(inventory.values())
# dict_values([50, 30, 20]).items() returns a view of all key-value pairs as tuples:
print(inventory.items())
# dict_items([('apple', 50), ('banana', 30), ('mango', 20)])These views are dynamic — they reflect any changes made to the dictionary after they're created. The .keys() view also supports set operations like union and intersection, which can be handy for comparing two dictionaries' key sets.
Adding and Updating
.setdefault(key, default=None) inserts a key with a default value only if that key isn't already present. If the key exists, it returns the existing value unchanged:
inventory.setdefault("grape", 0) # Adds 'grape': 0
inventory.setdefault("apple", 999) # 'apple' already exists — no change
print(inventory["apple"]) # 50.update(other) merges another dictionary (or iterable of pairs) into the current one. Existing keys get their values updated; new keys are appended:
defaults = {"timeout": 30, "retries": 3, "debug": False}
overrides = {"debug": True, "log_level": "INFO"}
defaults.update(overrides)
print(defaults)
# {'timeout': 30, 'retries': 3, 'debug': True, 'log_level': 'INFO'}You can also pass keyword arguments directly to .update():
defaults.update(timeout=60, version="2.0")Removing Data
.pop(key) removes an entry by key and returns its value. Optionally provide a fallback to avoid a KeyError when the key is absent:
removed = inventory.pop("banana")
print(removed) # 30inventory.pop("pineapple", None) # No error if key doesn't exist
.popitem() removes and returns the most recently inserted key-value pair as a tuple (last-in, first-out order):
last_item = inventory.popitem()
# Returns something like ('mango', 20)del removes an entry without returning the value:
del inventory["apple"].clear() removes all entries, leaving an empty dictionary:
inventory.clear()
print(inventory) # {}Operators With Dictionaries
Membership Testing: in and not in
Check whether a key exists in a dictionary:
config = {"host": "localhost", "port": 3000}
print("host" in config) # True
print("timeout" in config) # False
print("port" not in config) # FalseThis checks keys only, not values.
Equality: == and !=
Two dictionaries are equal if they contain the same key-value pairs, regardless of insertion order:
a = {"x": 1, "y": 2}
b = {"y": 2, "x": 1}
print(a == b) # TrueMerging With | and |=
Python 3.9 introduced the | operator for merging two dictionaries into a new one:
base = {"color": "blue", "size": "M"}
updates = {"size": "L", "material": "cotton"}
merged = base | updates
print(merged)
# {'color': 'blue', 'size': 'L', 'material': 'cotton'}The |= operator merges in-place (modifying the left-hand dictionary directly):
base |= updates
This is functionally similar to .update() but with a cleaner syntax when working with two dictionaries.
Built-in Functions With Dictionaries
Many of Python's built-in functions work naturally with dictionaries.
len() — number of key-value pairs:
print(len({"a": 1, "b": 2, "c": 3})) # 3sum() — total of all values (when they're numeric):
scores = {"Alice": 88, "Bob": 74, "Carol": 95}
print(sum(scores.values())) # 257min() and max() — smallest and largest values:
print(min(scores.values())) # 74
print(max(scores.values())) # 95To find the key with the highest value, use max() with a key argument:
print(max(scores, key=scores.get)) # Carol
sorted() — sort by keys, values, or items:
# Sort by key alphabetically
print(sorted(scores))
# ['Alice', 'Bob', 'Carol']
# Sort by value, highest first
print(sorted(scores, key=scores.get, reverse=True))
# ['Carol', 'Alice', 'Bob']any() and all() — check truthiness across values:
flags = {"feature_a": True, "feature_b": False, "feature_c": True}
print(any(flags.values())) # True — at least one is True
print(all(flags.values())) # False — not all are True
Iterating Over Dictionaries
Looping over a dictionary is something you'll do constantly in Python.
Iterating Over Keys
By default, iterating over a dictionary gives you its keys:
config = {"host": "localhost", "port": 8080, "debug": True}
for key in config:
print(key)
# host
# port
# debugYou can also call .keys() explicitly — the behaviour is the same:
for key in config.keys():
print(key)
Iterating Over Values
Use .values() when you only need the values:
for value in config.values():
print(value)
# localhost
# 8080
# True
Iterating Over Key-Value PairsThe most common loop pattern uses .items() to unpack both at once:
for key, value in config.items():
print(f"{key}: {value}")
# host: localhost
# port: 8080
# debug: TrueThis pattern is clean and avoids having to do config[key] inside the loop body.
Dictionary-Like Classes in the Standard Library
The collections module provides several specialised dictionary variants worth knowing about:
OrderedDict — preserves insertion order explicitly and supports order-based operations (less necessary since Python 3.7, but still useful for some ordering-specific logic)
defaultdict — automatically creates a default value for missing keys, which avoids KeyError and simplifies counting or grouping patterns
ChainMap — bundles multiple dictionaries together so they can be searched as one, without merging them
Counter — designed specifically for counting hashable objects; behaves like a dictionary where keys are items and values are counts
For example, defaultdict makes a word-frequency counter trivially easy:
from collections import defaultdict
word_count = defaultdict(int)
for word in "the cat sat on the mat the cat".split():
word_count[word] += 1
print(dict(word_count))
# {'the': 3, 'cat': 2, 'sat': 1, 'on': 1, 'mat': 1}Without defaultdict, you'd need to check whether each key exists before incrementing — or use .setdefault(). The defaultdict handles that automatically.
Summary
Python dictionaries are one of the most useful and versatile data structures in the language. Here's a quick recap of the ground covered:
Create dictionaries with {} literals, dict(), or .fromkeys()
Access values with square bracket notation or .get() for safe lookups
Add and update entries with direct assignment, .setdefault(), or .update()
Remove entries with .pop(), .popitem(), del, or .clear()
Use in to test for key membership, | to merge dictionaries, and == to compare them
Loop over keys, values, or pairs using for loops with .keys(), .values(), or .items()
Reach for defaultdict, Counter, or ChainMap from collections when the standard dict isn't quite enough
The more comfortable you become with dictionaries, the more naturally you'll reach for them when organising, processing, and transforming data in your Python projects.
Have a question about dictionaries or want to see a specific pattern covered? Leave a comment below.
Share this article
Loading comments...