Nested structures and working with real-world data
Real data is not a flat list. An API returns a list of users, each a dictionary, one field of which is a list of orders, each of those a dictionary. Nothing new is needed to handle that — just the four structures you already have, inside each other.
What it looks like
students = [
{"name": "Priya", "marks": [78, 85, 72]},
{"name": "Arjun", "marks": [65, 70, 80]},
{"name": "Sneha", "marks": [92, 88, 95]},
]
A list of dictionaries, each holding a string and a list. This is the single most common shape in real programming, and essentially what JSON is.
Reading into it
Work left to right, one step at a time:
print(students[0]) # the first dictionary
print(students[0]["name"]) # 'Priya'
print(students[0]["marks"]) # [78, 85, 72]
print(students[0]["marks"][1]) # 85
students[0]["marks"][1] looks intimidating written down and is simple read
aloud: take students, take the first, take its marks, take the second of those.
When a nested access fails, break it into steps:
student = students[0]
marks = student["marks"]
second = marks[1]
Now the error tells you exactly which step failed. Collapse it back once it works — or leave it, since the named version often reads better anyway.
Looping through
for student in students:
name = student["name"]
marks = student["marks"]
average = sum(marks) / len(marks)
print(f"{name:<8} {average:.1f}")
Priya 78.3
Arjun 71.7
Sneha 91.7
Nested loops when you need every individual value:
for student in students:
for mark in student["marks"]:
print(student["name"], mark)
The outer loop walks the students; the inner walks one student's marks.
Deeper nesting
company = {
"name": "RizTech Academy",
"offices": {
"pune": {
"staff": 12,
"teams": ["web", "mobile"],
},
"remote": {
"staff": 5,
"teams": ["devops"],
},
},
}
print(company["offices"]["pune"]["teams"][0]) # 'web'
Each bracket is one step down. Read it as a path.
Looping over nested dictionaries:
for city, office in company["offices"].items():
print(f"{city}: {office['staff']} staff")
for team in office["teams"]:
print(f" - {team}")
Note the single quotes inside the f-string: office['staff']. The f-string is
already using double quotes, so the inside must use the other kind. Modern
Python allows the same quote nested, but older versions do not, and mixing is
the habit that works everywhere.
Missing keys, several levels down
This is where nested data actually hurts:
print(company["offices"]["mumbai"]["staff"])
KeyError: 'mumbai'
The error names the key that failed, which is genuinely helpful — it tells you
the failure was at the offices level, not staff.
Chaining .get() protects each step:
staff = company.get("offices", {}).get("mumbai", {}).get("staff", 0)
print(staff) # 0
Each .get() falls back to an empty dictionary so the next call has something
to work on. It is wordy, and for two or three levels it is the right tool.
Beyond that, a check reads better:
offices = company.get("offices", {})
if "mumbai" in offices:
print(offices["mumbai"]["staff"])
else:
print("No Mumbai office.")
In module 6, try/except KeyError gives a third option that is often the
cleanest of all.
Changing nested data
Because lists and dictionaries are mutable, you can modify in place:
students[0]["marks"].append(90)
company["offices"]["pune"]["staff"] += 1
And the aliasing rule from the lists lesson still applies, now with more places to trip over:
first = students[0]
first["name"] = "Priyanka"
print(students[0]["name"])
Priyanka
first is not a copy. It is a second label on the dictionary inside the list.
That is usually what you want when updating records — and occasionally a
surprise when you thought you were working on a scratch copy.
Building nested data
The grouping pattern from the dictionaries lesson, one level deeper:
records = [
("Pune", "web", "Priya"),
("Pune", "mobile", "Arjun"),
("Delhi", "web", "Sneha"),
("Pune", "web", "Rahul"),
]
by_city = {}
for city, team, name in records:
if city not in by_city:
by_city[city] = {}
if team not in by_city[city]:
by_city[city][team] = []
by_city[city][team].append(name)
print(by_city)
{'Pune': {'web': ['Priya', 'Rahul'], 'mobile': ['Arjun']},
'Delhi': {'web': ['Sneha']}}
The two if not in checks exist to make sure each level exists before you reach
into it. It is repetitive; setdefault shortens it:
for city, team, name in records:
by_city.setdefault(city, {}).setdefault(team, []).append(name)
One line, and dense. Write the long version until the short one reads as obviously equivalent.
Printing it readably
Nested data printed with print() is a wall of brackets. Two better options:
import json
print(json.dumps(by_city, indent=2))
{
"Pune": {
"web": [
"Priya",
"Rahul"
],
...
Or pprint, which handles any Python object rather than just JSON-compatible
ones:
from pprint import pprint
pprint(by_city)
Both are debugging tools worth having. When nested data is misbehaving, seeing its actual shape solves most of it.
A realistic example
response = {
"status": "ok",
"data": {
"users": [
{"id": 1, "name": "Priya", "orders": [
{"item": "tea", "qty": 2, "price": 40},
{"item": "coffee", "qty": 1, "price": 120},
]},
{"id": 2, "name": "Arjun", "orders": []},
]
},
}
for user in response["data"]["users"]:
total = 0
for order in user["orders"]:
total += order["qty"] * order["price"]
print(f"{user['name']}: ₹{total}")
Priya: ₹200
Arjun: ₹0
Notice Arjun's empty order list needs no special handling — a for over an
empty list simply does not run, and total stays at zero. Setting the
accumulator before the loop is what makes that work.
Practice
- Build the
studentslist above. Print each student's name and highest mark. - Add a fourth student, then add a mark to an existing student.
- Find the student with the highest average.
- From
company, print every team across every office as one flat list. - Attempt
company["offices"]["mumbai"]["staff"], read theKeyError, then rewrite it safely two ways — chained.get()and anincheck. - Group
[("a", 1), ("b", 2), ("a", 3)]into{"a": [1, 3], "b": [2]}, first with explicitif not inchecks, then withsetdefault. - Build the nested
recordsgrouping above, then print it withjson.dumps(..., indent=2). - Given the
responsedictionary, find the single most expensive line item across all users.
Next: comprehensions — a shorter way to write the loops you have been writing all module.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship