RizTech Academy logo
RizTech Academy
Data StructuresLesson 6 of 825 min

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

  1. Build the students list above. Print each student's name and highest mark.
  2. Add a fourth student, then add a mark to an existing student.
  3. Find the student with the highest average.
  4. From company, print every team across every office as one flat list.
  5. Attempt company["offices"]["mumbai"]["staff"], read the KeyError, then rewrite it safely two ways — chained .get() and an in check.
  6. Group [("a", 1), ("b", 2), ("a", 3)] into {"a": [1, 3], "b": [2]}, first with explicit if not in checks, then with setdefault.
  7. Build the nested records grouping above, then print it with json.dumps(..., indent=2).
  8. Given the response dictionary, 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