Nested Lists and Dictionaries in Python
Learn to nest data in Python: lists inside lists (grids), dictionaries inside dictionaries, and lists of dictionaries for records. Index deep, loop through layers, and model real data. Runnable code, a worked example and a quiz.
Key takeaways
- Nesting means putting collections inside collections to model layered real-world data
- A list of lists makes a grid; index it twice, like grid[row][col]
- A list of dictionaries is the everyday way to store many records (rows of data)
- A dictionary of dictionaries groups related records under named keys
- Reach deeper one bracket at a time, and use nested loops to walk every layer
Real data has layers
So far your lists have held simple values like numbers or words, and your dictionaries have mapped single keys to single values. But real information is rarely that flat. A class isn't just a list of names — it's a list of students, and each student has a name, an age and several scores. A chessboard is a grid of squares. A shopping app stores many orders, and each order contains many items.
To model layered data, Python lets you put collections inside collections. This is called nesting, and it is one of the most useful ideas in everyday programming.
Lists inside lists: a grid
The simplest nesting is a list of lists, which gives you a 2D grid:
grid = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]
print(grid[0]) # [1, 2, 3] the whole first row
print(grid[1][2]) # 6 row 1, column 2
print(grid[2][0]) # 7 row 2, column 0
Reading the indexing carefully:
gridis a list whose items are themselves lists (the rows).grid[0]selects the first inner list — an entire row.grid[1][2]does it in two steps:grid[1]gives the row[4, 5, 6], then[2]takes its third item,6.
The rule is simple: each pair of brackets goes one level deeper, left to right.
Looping through a grid
To visit every cell, use two nested loops — the outer one walks the rows, the inner one walks the columns:
grid = [
[1, 2, 3],
[4, 5, 6],
]
for row in grid:
for cell in row:
print(cell, end=" ")
print() # newline after each row
This prints:
1 2 3
4 5 6
for row in gridgives each inner list in turn.for cell in rowthen walks the items inside that row.end=" "keeps the numbers on one line; the bareprint()after the inner loop starts a new line for the next row.
If nested loops are new to you, the lesson on nested loops and patterns shows more examples of this two-layer pattern.
A list of dictionaries: records
By far the most common nested structure in real programs is a list of dictionaries. Each dictionary is one record (think of a row in a spreadsheet), and the list holds them all:
students = [
{"name": "Ada", "age": 14, "score": 90},
{"name": "Brahim", "age": 15, "score": 76},
{"name": "Chen", "age": 14, "score": 88},
]
print(students[0]) # the whole first record
print(students[0]["name"]) # Ada
print(students[2]["score"]) # 88
students[0]selects the first dictionary in the list.students[0]["name"]then reads the"name"value from that record:"Ada".- Each record can have the same keys, giving you a clean, table-like structure.
Looping over records is now very natural:
for student in students:
print(student["name"], "scored", student["score"])
Ada scored 90
Brahim scored 76
Chen scored 88
Each pass gives you one whole dictionary in student, and you read its fields by key.
A dictionary of dictionaries
Sometimes you want to look records up by a name rather than a position. Then use a dictionary whose values are themselves dictionaries:
players = {
"ada": {"score": 90, "level": 5},
"brahim":{"score": 76, "level": 3},
}
print(players["ada"]["score"]) # 90
print(players["brahim"]["level"])# 3
# Add a new field to one record:
players["ada"]["wins"] = 12
print(players["ada"]) # {'score': 90, 'level': 5, 'wins': 12}
players["ada"]fetches Ada's inner dictionary in one step — no scanning needed.- A second
["score"]reaches into that inner dictionary. - You can add or change inner fields just like any dictionary, e.g.
players["ada"]["wins"] = 12.
Use a list of dicts when order matters or you process every record; use a dict of dicts when you mostly look records up by a unique name or id.
Reaching deep, safely
When data is nested several layers, two habits keep you sane. First, peel one layer at a time into named variables:
data = {"team": {"members": [{"name": "Ada"}, {"name": "Chen"}]}}
team = data["team"]
members = team["members"]
first = members[0]
print(first["name"]) # Ada
Each line names exactly one step, so a typo is easy to spot. Second, use .get() on dictionaries to avoid crashes when a key might be missing:
player = {"name": "Ada"}
print(player.get("score", 0)) # 0 (no 'score' key, so the default is used)
.get("score", 0) returns 0 instead of raising a KeyError when the key is absent — a safe way to read optional fields.
Worked example: a small gradebook
Let's combine the patterns. We have a list of student records and want to find the top scorer and the class average.
students = [
{"name": "Ada", "scores": [90, 85, 92]},
{"name": "Brahim", "scores": [70, 75, 80]},
{"name": "Chen", "scores": [88, 91, 84]},
]
best_name = None
best_avg = -1
class_total = 0
for student in students:
scores = student["scores"]
avg = sum(scores) / len(scores)
class_total += avg
print(student["name"], "average:", round(avg, 1))
if avg > best_avg:
best_avg = avg
best_name = student["name"]
print("Top student:", best_name, "with", round(best_avg, 1))
print("Class average:", round(class_total / len(students), 1))
Output:
Ada average: 89.0
Brahim average: 75.0
Chen average: 87.7
Top student: Ada with 89.0
Class average: 83.9
How it works:
- Each
studentis a dictionary;student["scores"]is a nested list of that student's marks. sum(scores) / len(scores)computes each personal average.- We track the best average seen so far in
best_avgand remember the matching name — a classic "find the maximum" pattern. class_totalaccumulates the averages so we can divide for the overall class average at the end.
This single small program touches every idea in the lesson: a list of dictionaries, a list nested inside each dictionary, and a loop that reaches into both layers.
Try it yourself
Build a tiny library catalogue:
- Make a list of dictionaries, each with
"title","author"and"available"(a boolean). - Loop through and print only the books where
"available"isTrue. - Add a nested list of
"tags"to each book and print the first tag of the first book withbooks[0]["tags"][0]. - Turn it into a dictionary of dictionaries keyed by title, and look one book up directly by its title.
When your records get more complex, see how the same data could be saved and reloaded in reading and writing files in Python.
Quick quiz
Test yourself and earn XP
Given grid = [[1, 2], [3, 4]], what is grid[1][0]?
grid[1] is the second inner list [3, 4]; then [0] takes its first item, which is 3.
How would you store many student records, each with a name and score?
A list of dictionaries is the standard pattern: each record is a dict, and the list holds all the records.
Given data = {"ada": {"score": 90}}, how do you read Ada's score?
First data["ada"] gets the inner dictionary, then ["score"] reads the value inside it: 90.
To print every cell of a 2D list, you typically use…
The outer loop walks the rows; the inner loop walks each item within a row, so nested loops visit every cell.
In a list of dictionaries called people, how do you get the first person's name?
people[0] selects the first dictionary, then ["name"] reads that record's name value.
FAQ
There is no practical hard limit — you can put lists inside dictionaries inside lists inside dictionaries as deeply as your data needs. In real programs you regularly see two or three levels, for example a list of customers where each customer is a dictionary that contains a list of their orders, and each order is itself a dictionary. The thing that limits you is not Python but readability: every extra level means more brackets to track and more chances to mistype a key or index. When nesting gets deep, give the inner pieces their own variables (person = people[0]; orders = person["orders"]) so each step is named and easy to follow, and consider whether a class might model the data more clearly.
Because it essentially is the same shape. JSON, the format used everywhere to send data between websites and apps, is built from exactly these pieces: ordered lists (arrays) and key-value objects (dictionaries) nested inside one another. Python's lists and dictionaries map almost one-to-one onto JSON arrays and objects, which is why Python is so popular for working with web data and APIs. Once you are comfortable nesting lists and dictionaries here, you already understand the structure of most real-world data you will load from a file or download from the internet.
Keep exploring
More in Coding