Python Dictionaries: The Complete Beginner's Guide
Lists are positional. Dictionaries are labeled. This chapter covers key-value access, safe missing-key patterns, \`items()\` iteration, nested dictionaries, comprehensions, and practical merge/update techniques used in real API and config code.
Chapter 7 of 20 · Beginner · 35 min · Python Programming Course
Lists store items by position. You access them by index — items[0], items[3]. That works well when position has meaning. But most real data is not positional — it is labelled. A user has a name, an age, an email. A product has a price, a category, a stock count. A server response has a status code, a message, a payload.
When data has labels, dictionaries are the right tool. A dictionary stores key-value pairs — instead of accessing data by its position, you access it by a meaningful name you choose. user["name"] is clearer than user[0]. product["price"] is safer than hoping price is always the third item in a list.
Dictionaries are the backbone of most real Python programs. JSON APIs return dictionaries. Configuration files become dictionaries. Database rows become dictionaries. Once you understand this data structure deeply, a large portion of real-world Python code becomes readable.
What You Will Learn in This Chapter
By the end of this tutorial you will be able to:
- Create dictionaries using literals and the
dict()constructor - Access values using bracket notation and
get() - Add, update, and delete key-value pairs
- Use all essential dictionary methods:
keys(),values(),items(),get(),pop(),update(),setdefault(),copy(), andclear() - Iterate over dictionaries using loops
- Handle missing keys safely without crashing
- Build nested dictionaries for structured data
- Write dictionary comprehensions
- Merge dictionaries using multiple approaches
- Avoid the most common dictionary mistakes beginners make
Estimated time: 35 minutes reading + 20 minutes practice
Keep a REPL or editor open while reading. Dictionaries become intuitive only when you type the examples, mutate keys, inspect items() output, and intentionally trigger a few KeyError cases to see when get() is the safer choice.
One mental model helps everywhere: a list answers “what is at position i?”, while a dictionary answers “what value belongs to this label?” Once that distinction clicks, JSON payloads, API responses, and configuration objects stop feeling noisy and start feeling structured.
What Is a Python Dictionary?
A dictionary is an ordered, mutable collection of key-value pairs. Every value is stored with a unique key that you use to retrieve it — like looking up a word in a physical dictionary to find its definition.
person = {
"name": "Elena",
"age": 30,
"city": "Mumbai",
"is_enrolled": True
}
print(person)
# {'name': 'Elena', 'age': 30, 'city': 'Mumbai', 'is_enrolled': True}
Four properties to understand immediately:
- Keys must be unique. If you use the same key twice, the second value silently overwrites the first. No error, no warning.
- Keys must be hashable. Strings, integers, floats, and tuples of hashable items can be keys. Lists and other dicts cannot — they are mutable and therefore not hashable.
- Values can be anything. Strings, numbers, booleans, lists, other dictionaries, functions — any Python object can be a dictionary value.
- Ordered as of Python 3.7. Dictionaries maintain insertion order. When you iterate or print a dictionary, items appear in the order they were added.
Creating Dictionaries
Dictionary Literals — Curly Braces With Colons
# Empty dictionary
empty = {}
# String keys — most common
student = {
"name": "Arjun",
"age": 21,
"gpa": 3.8,
"courses": ["Python", "SQL", "Statistics"]
}
# Integer keys
squares = {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
# Mixed key types (valid but uncommon)
mixed_keys = {"name": "Alice", 1: "one", (0, 0): "origin"}
The dict() Constructor
# From keyword arguments — keys must be valid Python identifiers
person = dict(name="Bob", age=25, city="Delhi")
print(person) # {'name': 'Bob', 'age': 25, 'city': 'Delhi'}
# From a list of key-value pairs
pairs = [("name", "Carol"), ("age", 28), ("city", "Chennai")]
person = dict(pairs)
print(person) # {'name': 'Carol', 'age': 28, 'city': 'Chennai'}
# From two separate lists using zip()
keys = ["name", "age", "city"]
values = ["David", 32, "Pune"]
person = dict(zip(keys, values))
print(person) # {'name': 'David', 'age': 32, 'city': 'Pune'}
dict(zip(keys, values)) is a pattern you will use constantly when building dictionaries from data sources — CSV columns, API fields, database rows.
Accessing Dictionary Values
Bracket Notation — Direct Access
student = {"name": "Priya", "age": 20, "gpa": 3.9}
print(student["name"]) # Priya
print(student["gpa"]) # 3.9
Bracket notation raises a KeyError if the key does not exist:
# print(student["email"])
# KeyError: 'email'
get() — Safe Access With a Default
get() returns the value if the key exists, or a default value if it does not — without raising an error:
student = {"name": "Priya", "age": 20}
print(student.get("name")) # Priya
print(student.get("email")) # None — key missing, no error
print(student.get("email", "N/A")) # N/A — custom default
print(student.get("age", 0)) # 20 — key exists, returns value
When to use get() vs brackets:
Use bracket notation when the key must exist and its absence is a bug — the KeyError tells you something went wrong. Use get() when the key might legitimately be absent and you have a sensible default.
# API response — field might not always be present
response = {"status": 200, "data": {"user": "alice"}}
username = response.get("data", {}).get("user", "anonymous")
print(username) # alice
error_msg = response.get("error", "No error")
print(error_msg) # No error
Checking Key Existence
Always use in to check whether a key exists before accessing it:
student = {"name": "Priya", "age": 20}
print("name" in student) # True
print("email" in student) # False
print("email" not in student) # True
# Safe access pattern
if "email" in student:
print(student["email"])
else:
print("No email on record")
in checks keys by default. To check values, use in student.values():
print(20 in student.values()) # True
print("Priya" in student.values()) # True
print("Bob" in student.values()) # False
Modifying Dictionaries
Adding and Updating Items
Assignment adds a new key if it does not exist, or updates the value if it does:
student = {"name": "Arjun", "age": 21}
# Add new key
student["email"] = "arjun@example.com"
student["gpa"] = 3.7
print(student)
# Update existing key
student["age"] = 22
student["gpa"] = 3.9
print(student)
update() — Add or Update Multiple Keys at Once
student = {"name": "Arjun", "age": 21}
student.update({"age": 22, "email": "arjun@example.com", "city": "Delhi"})
print(student)
# update() also accepts keyword arguments
student.update(gpa=3.9, is_enrolled=True)
print(student)
update() is the cleanest way to apply multiple changes at once — common when merging configuration data or applying API response fields to a local record.
setdefault() — Add a Key Only if It Does Not Exist
student = {"name": "Arjun", "age": 21}
# Adds "email" with default value because key is missing
student.setdefault("email", "not_provided@example.com")
print(student["email"]) # not_provided@example.com
# Does NOT overwrite because "name" already exists
student.setdefault("name", "Unknown")
print(student["name"]) # Arjun — unchanged
setdefault() is useful for initialising nested structures safely without overwriting existing data:
# Safely initialise a list for a new key
scores = {}
scores.setdefault("Alice", []).append(88)
scores.setdefault("Alice", []).append(92)
scores.setdefault("Bob", []).append(75)
print(scores) # {'Alice': [88, 92], 'Bob': [75]}
Removing Items From Dictionaries
pop() — Remove by Key and Return the Value
student = {"name": "Arjun", "age": 21, "gpa": 3.7, "city": "Delhi"}
removed = student.pop("city")
print(removed) # Delhi
print(student) # {'name': 'Arjun', 'age': 21, 'gpa': 3.7}
# With a default — avoids KeyError if key is missing
val = student.pop("email", None)
print(val) # None — no error
popitem() — Remove and Return the Last Inserted Item
student = {"name": "Arjun", "age": 21, "gpa": 3.7}
key, value = student.popitem()
print(key, value) # gpa 3.7 — last inserted item
print(student) # {'name': 'Arjun', 'age': 21}
popitem() is useful when processing a dictionary as a stack — removing and processing items one at a time.
del — Remove by Key
student = {"name": "Arjun", "age": 21, "gpa": 3.7}
del student["gpa"]
print(student) # {'name': 'Arjun', 'age': 21}
# del student["email"] # KeyError if key does not exist
clear() — Empty the Entire Dictionary
student = {"name": "Arjun", "age": 21}
student.clear()
print(student) # {} — empty dict, variable still exists
Iterating Over Dictionaries
This is where dictionaries become genuinely powerful. Three views let you iterate in different ways.
Iterating Over Keys
student = {"name": "Priya", "age": 20, "city": "Bangalore", "gpa": 3.9}
# Default iteration gives keys
for key in student:
print(key)
# Explicit — same result
for key in student.keys():
print(key)
Iterating Over Values
for value in student.values():
print(value)
Iterating Over Key-Value Pairs With items()
This is the most common and most useful iteration pattern:
for key, value in student.items():
print(f"{key}: {value}")
Output:
name: Priya
age: 20
city: Bangalore
gpa: 3.9
items() is the pattern you will use in the vast majority of real code when you need both the key and the value together.
# Building a formatted profile from a dictionary
profile = {
"Name": "Priya Sharma",
"Age": 20,
"City": "Bangalore",
"GPA": 3.9,
"Enrolled": True
}
print("=== Student Profile ===")
for field, data in profile.items():
print(f"{field:<12}: {data}")
Output:
=== Student Profile ===
Name : Priya Sharma
Age : 20
City : Bangalore
GPA : 3.9
Enrolled : True
Copying Dictionaries — The Same Trap as Lists
Just like lists in Day 5, assigning a dictionary to a new variable does not copy it — both names reference the same object:
original = {"name": "Alice", "score": 88}
alias = original # NOT a copy
alias["score"] = 100
print(original) # {'name': 'Alice', 'score': 100} — changed!
Create a true independent copy with .copy():
original = {"name": "Alice", "score": 88}
copy = original.copy()
copy["score"] = 100
print(original) # {'name': 'Alice', 'score': 88} — unchanged
print(copy) # {'name': 'Alice', 'score': 100}
.copy() creates a shallow copy — sufficient when values are simple types. For dictionaries containing other mutable objects like lists or nested dicts, use copy.deepcopy():
import copy
original = {"name": "Alice", "scores": [88, 92, 79]}
deep = copy.deepcopy(original)
deep["scores"].append(95)
print(original["scores"]) # [88, 92, 79] — unchanged
print(deep["scores"]) # [88, 92, 79, 95]
Nested Dictionaries — Dictionaries Inside Dictionaries
Real data is rarely flat. A user has a profile, address, and list of orders. A product has pricing, inventory, and categories. Nested dictionaries represent this structure naturally:
company = {
"name": "Schoolabe",
"founded": 2024,
"team": {
"ceo": {"name": "Rohit", "age": 32},
"cto": {"name": "Ananya", "age": 29},
},
"products": ["Python Course", "Kafka Course", "DSA Course"]
}
# Accessing nested values
print(company["name"]) # Schoolabe
print(company["team"]["ceo"]["name"]) # Rohit
print(company["products"][0]) # Python Course
# Adding to a nested structure
company["team"]["designer"] = {"name": "Kabir", "age": 26}
print(company["team"]["designer"]) # {'name': 'Kabir', 'age': 26}
Iterating Over Nested Dictionaries
students = {
"S001": {"name": "Alice", "grade": "A", "score": 92},
"S002": {"name": "Bob", "grade": "B", "score": 83},
"S003": {"name": "Carol", "grade": "A", "score": 95},
}
for student_id, details in students.items():
print(f"{student_id}: {details['name']} — {details['grade']} ({details['score']})")
Output:
S001: Alice — A (92)
S002: Bob — B (83)
S003: Carol — A (95)
Dictionary Comprehensions
Dictionary comprehensions create a new dictionary by applying an expression to each item in an iterable — the same idea as list comprehensions but producing key-value pairs:
# Basic syntax
new_dict = {key_expr: value_expr for item in iterable}
# Square numbers as a dictionary
squares = {n: n ** 2 for n in range(1, 6)}
print(squares) # {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
# Word lengths
words = ["python", "java", "javascript", "go", "rust"]
lengths = {word: len(word) for word in words}
print(lengths)
# Invert a dictionary — swap keys and values
original = {"a": 1, "b": 2, "c": 3}
inverted = {v: k for k, v in original.items()}
print(inverted) # {1: 'a', 2: 'b', 3: 'c'}
With a Condition
scores = {"Alice": 88, "Bob": 55, "Carol": 92, "David": 61, "Eve": 78}
# Only students who passed (60+)
passed = {name: score for name, score in scores.items() if score >= 60}
print(passed)
# Grade assignment
def grade(score):
return "A" if score >= 85 else "B" if score >= 70 else "C" if score >= 60 else "F"
graded = {name: grade(score) for name, score in scores.items()}
print(graded)
Merging Dictionaries
Three ways to merge dictionaries, each with different behavior:
update() — Merge Into Existing Dictionary
defaults = {"theme": "light", "language": "en", "notifications": True}
user_prefs = {"theme": "dark", "font_size": 14}
defaults.update(user_prefs) # user_prefs values overwrite defaults
print(defaults)
| Operator — Create New Merged Dictionary (Python 3.9+)
defaults = {"theme": "light", "language": "en"}
user_prefs = {"theme": "dark", "font_size": 14}
merged = defaults | user_prefs # right side wins on conflicts
print(merged)
# Original dicts unchanged
print(defaults)
** Unpacking — Works in All Python 3 Versions
merged = {**defaults, **user_prefs} # right side wins on conflicts
print(merged)
The ** unpacking approach is the most widely compatible — use it when you need to support Python versions before 3.9.
Handling Missing Keys — Three Patterns
Missing keys are one of the most common sources of bugs in dictionary-heavy code. Three patterns handle them cleanly.
Pattern 1: Check First With in
config = {"host": "localhost", "port": 5432}
if "password" in config:
password = config["password"]
else:
password = "default_password"
Pattern 2: Use get() With a Default
password = config.get("password", "default_password")
Cleaner and more Pythonic than the if/else version.
Pattern 3: collections.defaultdict
When you need a dictionary where every missing key automatically gets a default value:
from collections import defaultdict
# Default value is an empty list for every missing key
word_positions = defaultdict(list)
sentence = "the quick brown fox jumps over the lazy fox"
for i, word in enumerate(sentence.split()):
word_positions[word].append(i)
print(dict(word_positions))
Without defaultdict you would need setdefault() or an if key not in dict check before every append(). defaultdict eliminates that boilerplate entirely.
A Complete Working Program
Here is a student grade management system that uses every dictionary concept from this chapter together:
# Student grade management system
students = {
"S001": {"name": "Priya Sharma", "scores": [88, 92, 79, 95], "enrolled": True},
"S002": {"name": "Arjun Mehta", "scores": [72, 68, 81, 75], "enrolled": True},
"S003": {"name": "Cleo Park", "scores": [95, 98, 92, 97], "enrolled": True},
"S004": {"name": "Ravi Kumar", "scores": [55, 60, 58, 62], "enrolled": False},
"S005": {"name": "Maya Torres", "scores": [83, 87, 90, 85], "enrolled": True},
}
def calculate_average(scores):
return sum(scores) / len(scores)
def assign_grade(average):
if average >= 90: return "A"
elif average >= 80: return "B"
elif average >= 70: return "C"
elif average >= 60: return "D"
else: return "F"
# Build report using dict comprehension
report = {
sid: {
"name": data["name"],
"average": round(calculate_average(data["scores"]), 1),
"grade": assign_grade(calculate_average(data["scores"])),
"enrolled": data["enrolled"]
}
for sid, data in students.items()
}
# Print full report
print(f"{'ID':<6} {'Name':<18} {'Average':>8} {'Grade':>6} {'Status':>10}")
print("-" * 52)
for sid, result in report.items():
status = "Active" if result["enrolled"] else "Inactive"
print(f"{sid:<6} {result['name']:<18} {result['average']:>8} "
f"{result['grade']:>6} {status:>10}")
# Summary statistics
active = {sid: r for sid, r in report.items() if r["enrolled"]}
averages = [r["average"] for r in active.values()]
print(f"\n=== Summary ===")
print(f"Total students: {len(report)}")
print(f"Active students: {len(active)}")
print(f"Class average: {sum(averages)/len(averages):.1f}")
print(f"Top student: {max(active.items(), key=lambda x: x[1]['average'])[1]['name']}")
Output:
ID Name Average Grade Status
----------------------------------------------------
S001 Priya Sharma 88.5 B Active
S002 Arjun Mehta 74.0 C Active
S003 Cleo Park 95.5 A Active
S004 Ravi Kumar 58.8 F Inactive
S005 Maya Torres 86.2 B Active
=== Summary ===
Total students: 5
Active students: 4
Class average: 86.0
Top student: Cleo Park
5 Dictionary Mistakes Every Beginner Makes
Mistake 1: Using bracket notation for a key that might not exist
config = {"host": "localhost"}
# print(config["port"]) # KeyError: 'port'
# Fix
print(config.get("port", 5432)) # 5432
Mistake 2: Duplicate keys silently overwriting each other
data = {"name": "Alice", "age": 25, "name": "Bob"}
print(data) # {'name': 'Bob', 'age': 25} — Alice silently lost
Python does not warn you about duplicate keys. The last value wins. Always check for accidental duplication when building dictionaries from data sources.
Mistake 3: Iterating and modifying a dictionary simultaneously
scores = {"Alice": 88, "Bob": 55, "Carol": 92}
# Wrong — RuntimeError: dictionary changed size during iteration
# for name in scores:
# if scores[name] < 60:
# del scores[name]
# Fix — iterate over a copy of keys
for name in list(scores.keys()):
if scores[name] < 60:
del scores[name]
# Cleaner fix — dict comprehension
scores = {name: score for name, score in scores.items() if score >= 60}
Mistake 4: Forgetting that assignment aliases, not copies
original = {"name": "Alice", "score": 88}
copy = original # same object in memory
copy["score"] = 100
print(original["score"]) # 100 — original changed unexpectedly
# Fix
copy = original.copy()
Mistake 5: Assuming keys(), values(), and items() return lists
student = {"name": "Alice", "age": 20}
keys = student.keys()
print(type(keys)) # <class 'dict_keys'> — not a list
# These are dynamic views — they update when the dict changes
student["city"] = "Delhi"
print(keys) # dict_keys(['name', 'age', 'city']) — updated automatically
# Convert to list when you need list operations
keys_list = list(student.keys())
Practice: Dictionary Exercises
Exercise 1: Build and access
Create a dictionary representing a book with keys for title, author, year, genre, and rating. Access each value using both bracket notation and get(). Try accessing a key that does not exist using get() with a default.
Exercise 2: Update and remove
Start with a product dictionary containing name, price, and stock. Add a category key, update the price, remove the stock key using pop(), and print the result at each step.
Exercise 3: Iterate with items()
Given a dictionary of country-capital pairs for five countries, iterate over it using items() and print each pair in the format "The capital of [country] is [capital]".
Exercise 4: Dictionary comprehension
Given temperatures = {"Delhi": 38, "Mumbai": 32, "Bangalore": 26, "Chennai": 35, "Kolkata": 34}, create a new dictionary containing only cities where the temperature exceeds 33 degrees.
Exercise 5: Nested dictionary
Build a nested dictionary for three students, each with a name, a list of three scores, and an enrolled status. Write a loop that prints each student's name and their average score.
Exercise 6: Word frequency counter
Write a program that takes a sentence, splits it into words, and builds a dictionary counting how many times each word appears. Print the words sorted by frequency in descending order.
→ See all Python practice exercises with solutions
For all topics, see Python exercises.
What Comes Next — Day 8: Conditional Statements
You now have the complete beginner toolkit for Python's core data structures — lists, tuples, sets, and dictionaries. Day 8 puts them to work: conditional statements let your programs make decisions. Every if statement evaluates a condition and chooses a path — and now that you have rich data structures to query, your conditions can be meaningful and complex.
Day 8 covers:
- if, elif, and else — the full decision structure
- Nested conditions and compound conditions
- Ternary expressions — one-line conditionals
- Common patterns: range checks, membership tests, type checks
- Writing conditions that are readable, not just correct
→ Continue to Day 8: Conditional Statements
Chapter navigation
- Previous: Day 6: Tuples and Sets
- Next: Day 8: Conditional Statements
Frequently asked questions: Dictionaries
What is a dictionary in Python?
A dictionary is an ordered, mutable collection that stores data as key-value pairs. Each value is associated with a unique key — you use the key to retrieve its value, similar to looking up a word in a physical dictionary. Dictionaries are one of the most used data structures in Python because most real-world data — JSON responses, configuration files, database records — naturally fits the key-value model.
What is the difference between dict[key] and dict.get(key) in Python?
Bracket notation raises a KeyError if the key does not exist. get() returns None by default, or a custom default you specify, without raising an error. Use brackets when the key must exist and its absence is a bug. Use get() when the key might legitimately be missing and you have a sensible default value.
Can Python dictionary keys be any data type?
Keys must be hashable — immutable types work: strings, integers, floats, tuples of hashable items. Mutable types cannot be keys: lists, sets, and other dicts are not hashable. In practice, string keys are by far the most common because they produce the most readable code.
How do I iterate over a Python dictionary?
Three methods cover all cases. for key in dict iterates over keys. for value in dict.values() iterates over values. for key, value in dict.items() iterates over key-value pairs together — this is the most common pattern because you usually need both the key and value in the loop body.
What is a nested dictionary in Python?
A nested dictionary is a dictionary where one or more values are themselves dictionaries. This structure represents hierarchical data naturally — a user with a profile and an address, a company with departments and employees. Access nested values by chaining bracket notation: data["user"]["address"]["city"].
What is a dictionary comprehension in Python?
A dictionary comprehension creates a new dictionary using a compact expression: {key_expr: value_expr for item in iterable}. It is the dict equivalent of a list comprehension — cleaner and often faster than building a dictionary with a loop and repeated assignment. Add a condition with if to filter which items are included.
How do I safely handle missing keys in a Python dictionary?
Three approaches: use in to check before accessing, use get() with a default value, or use collections.defaultdict when every missing key should automatically receive the same default. get() is the most concise for single lookups. defaultdict is best when you are accumulating data into lists or sets under dictionary keys.
What is the difference between pop() and del for removing dictionary items?
del dict[key] removes the key-value pair but returns nothing. dict.pop(key) removes the pair and returns the value, which you can use immediately. Both raise KeyError if the key is missing — unless you provide a default to pop(): dict.pop(key, None) returns None instead of raising an error.
Related Page
Day 6: Tuples and Sets
Learn about day 6: tuples and sets
Learn MoreDay 8: Conditional Statements
Continue with day 8: conditional statements
Learn MorePython exercises hub
165+ programs with solutions
Learn MorePython quiz
Multiple-choice checks with explanations
Learn MoreBasic Python programs
Practice loops and dictionary patterns
Learn More