Organizing Nested Data

Introduction

Welcome to the fourth and final unit of Organizing Data with Dictionaries in Python! We have created dictionaries and read from them safely, modified their entries with assignment and update(), and swept through them end to end with keys(), values(), and items(). That is a complete toolkit for one level of data.

And "one level" is exactly the limitation we are about to remove. Every dictionary we have built so far stored flat values: a single number or a single string. Real data is rarely that shallow. A user has a name and a list of roles. A team has a label and a list of members. Squeezing that into a flat dictionary means losing information.

The answer is nesting: placing collections inside other collections. In this lesson, we will work with the two shapes that appear most often in practice:

  1. A list of dictionaries, which holds many records of the same kind;
  2. A dictionary of lists, which groups many items under one label.

Everything we use here is already familiar: indices, keys, append(), len(), and for loops. Only the combination is new.

A List of Dictionaries: Modeling Many Records

Let us start with the shape you will meet most often when data comes from a file, a database, or an API: a list where every item is a dictionary describing one thing.

Python
users = [
    {"name": "Ada", "roles": ["admin", "editor"]},
    {"name": "Max", "roles": ["viewer"]},
]

There are three layers stacked here, so let us name them out loud:

  • users is a list holding two items, which is why len(users) is 2 and not 4; the outer list only counts its direct members.
  • Each item is a dictionary with the same two keys, "name" and "roles". Sharing keys across records is what makes the collection easy to process later.
  • The value stored under "roles" is itself a list of strings, so one user can hold any number of roles.

Notice the formatting: one record per line, with a trailing comma. Python does not require it, but it keeps records readable and makes adding another one a one-line change. As you may recall from the lists course, nested lists and chained indexing are not brand new; mixing lists with dictionaries is the fresh part.

Reaching Nested Data with Chained Indices and Keys

To pull a value out of a nested structure, we peel one layer at a time and read the expression left to right.

Python
# Reach nested data by chaining indices and keys
print("First user's name:", users[0]["name"])
print("First user's first role:", users[0]["roles"][0])

In the first line, users[0] hands us the first dictionary, and ["name"] then pulls the string "Ada" out of it. The second line goes one step further: users[0] gives us the dictionary, ["roles"] gives us the list ["admin", "editor"], and [0] grabs its first element.

The rule of thumb is short: lists use integer indices or slices, while dictionaries use hashable keys. Keys are often strings, but integers and tuples of hashable values are valid too. A missing dictionary key—including a missing integer key—raises KeyError; a list position that does not exist raises IndexError; and TypeError can occur for an unsupported list index or an unhashable dictionary key such as a list or dictionary. Those errors are useful hints, not mysteries.

text
First user's name: Ada
First user's first role: admin

A Dictionary of Lists: Grouping Under Labels

Now let us flip the arrangement. Instead of many records sitting in a list, we can use a dictionary whose values are lists, which is ideal when several items belong under one shared label.

Python
# A dictionary whose values are lists
team = {
    "backend": ["Ada", "Sam"],
    "frontend": ["Max"],
}

We read this as follows: the key "backend" maps to a list of two names, and the key "frontend" maps to a list of one name. Access works the same way as before, one layer at a time: team["backend"] gives us the list, so team["backend"][1] gives us "Sam".

The diagram below lays both shapes next to each other, with their layers labeled:

Diagram comparing a list of user dictionaries with a dictionary mapping team labels to lists of names

Choosing between them is a design decision, and this table sums up the trade-off:

ShapeLooks likeReach for it when
List of dictionaries[{...}, {...}]Many records that share the same fields
Dictionary of lists{"label": [...]}Items grouped under named categories

Modifying a Nested Collection In Place

A nested list is not a special read-only object; it is an ordinary list that happens to live inside a dictionary. Once we have reached it, every method from the lists course is available.

Python
team["backend"].append("Lee")
print("Backend team:", team["backend"])

Think of this as two steps happening in order. First, team["backend"] retrieves the actual list stored in the dictionary, not a copy of it. Second, .append("Lee") mutates that very list, so the new name is visible through team from then on.

A common slip is writing team.append("Lee"), which raises an AttributeError because dictionaries have no append() method. The list is one layer deeper, so the key lookup has to come first. The same pattern works on our other structure, too: users[1]["roles"].append("editor") would give Max a second role.

text
Backend team: ['Ada', 'Sam', 'Lee']

Looping Through Records and Their Nested Lists

Iteration and nesting combine naturally: we loop over the outer list, and inside the loop, we use keys to read fields from whichever record we are holding.

Python
# Loop through a list of records and their nested lists
for user in users:
    print(f"{user['name']} has {len(user['roles'])} role(s)")

On each pass, the loop variable user is bound to one whole dictionary, so user["name"] and user["roles"] read that single record. Because user['roles'] is a list, len() counts the roles of that user alone: 2 for Ada, then 1 for Max.

One practical detail about the f-string: it is wrapped in double quotes, so the keys inside it use single quotes. Reusing double quotes there would end the string early. If we wanted to print each role on its own line, a second for role in user["roles"]: loop nested inside this one would do it.

text
Ada has 2 role(s)
Max has 1 role(s)

Putting It All Together

Here is the complete program, with each piece in the order we built it: the records, the chained access, the grouped dictionary, the in-place append, and the record loop.

Python
users = [
    {"name": "Ada", "roles": ["admin", "editor"]},
    {"name": "Max", "roles": ["viewer"]},
]

# Reach nested data by chaining indices and keys
print("First user's name:", users[0]["name"])
print("First user's first role:", users[0]["roles"][0])

# A dictionary whose values are lists
team = {
    "backend": ["Ada", "Sam"],
    "frontend": ["Max"],
}
team["backend"].append("Lee")
print("Backend team:", team["backend"])

# Loop through a list of records and their nested lists
for user in users:
    print(f"{user['name']} has {len(user['roles'])} role(s)")

Every printed line traces back to one statement: the first two come from chained access into users, the third shows team["backend"] after its append, and the last two come from the loop over records.

text
First user's name: Ada
First user's first role: admin
Backend team: ['Ada', 'Sam', 'Lee']
Ada has 2 role(s)
Max has 1 role(s)

Conclusion and Next Steps

Excellent work: our data can now have real depth. We met two nesting shapes — a list of dictionaries for many records that share fields and a dictionary of lists for items grouped under labels — and one access rule unlocks both: peel a single layer at a time, choosing the bracket that matches the collection currently in your hands. We also saw that nested values are ordinary objects, so a list reached through a key can be appended to in place, and a list of records can be looped over exactly like any other list, with keys reading the fields inside each pass.

With that, you have finished Organizing Data with Dictionaries in Python, moving from a first key-value pair all the way to structured records that mirror real systems. The practices ahead let you drive: reaching a nested value through a chain, appending a name to a role list, looping through records to count their nested items, and building a list of dictionaries from scratch. Next up, the following course brings strings, lists, and dictionaries together for genuine data processing work: turning lines of raw text into a list of records like the ones we built here, counting and grouping what they contain, and summarizing the result into a report. So let us go nest some data and finish this course strong!

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal