How to create a list of lists in Python
Learn how to create a list of lists in Python. Explore different methods, tips, real-world applications, and how to debug common errors.

A list of lists in Python is a versatile data structure, perfect for tasks that require a matrix or grid. It's a fundamental skill for any developer who works with complex data.
In this article, we'll cover several techniques to create these nested structures, from simple loops to list comprehensions. We'll also provide practical tips, real-world applications, and debugging advice to help you avoid common pitfalls.
Creating a basic list of lists
nested_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
print(nested_list)--OUTPUT--[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
The most direct method for creating a list of lists is to define it manually. In the example, nested_list is assigned a list literal where each element is another list. This approach is ideal for static data that won't change, as it's explicit and easy to read.
Think of it as building a small grid or matrix:
- The outer brackets
[]create the main list container. - Each inner list, like
[1, 2, 3], becomes a row in your grid.
This structure is intuitive because what you write is exactly what you get—a straightforward 2D array useful for organizing data in rows and columns.
Basic creation techniques
When your data isn't static, you can build nested lists programmatically with list comprehensions or the append() method, which is perfect for creating dynamic matrices.
Using list comprehension for nested lists
rows, cols = 3, 2
nested_list = [[i*cols + j + 1 for j in range(cols)] for i in range(rows)]
print(nested_list)--OUTPUT--[[1, 2], [3, 4], [5, 6]]
List comprehension offers a compact and powerful way to generate nested lists. Think of it as a nested loop packed into a single, readable line of code.
- The outer comprehension,
for i in range(rows), is responsible for creating each row. - The inner one,
for j in range(cols), runs for each row to populate it with elements. - The expression
i*cols + j + 1calculates the unique value for every position in the grid.
This approach is often more efficient and Pythonic than writing out full for loops.
Building lists of lists with append()
outer_list = []
for i in range(3):
inner_list = [i+j for j in range(3)]
outer_list.append(inner_list)
print(outer_list)--OUTPUT--[[0, 1, 2], [1, 2, 3], [2, 3, 4]]
Using a for loop with the append() method is another great way to build a list of lists, especially when the logic gets complex. This approach is more verbose than a list comprehension but can be easier to follow.
- First, you initialize an empty list,
outer_list. - The loop then iterates, creating a new
inner_listeach time. - Finally,
outer_list.append(inner_list)adds the newly created list as a new row.
This step-by-step process gives you more control and makes debugging simpler.
Creating matrices with nested lists
matrix = []
for i in range(3):
row = [i * 3 + j + 1 for j in range(3)]
matrix.append(row)
print(matrix)--OUTPUT--[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
You can efficiently build a matrix by combining a for loop with a list comprehension. This method gives you the readability of a loop while keeping the row creation logic concise and powerful.
- The outer
forloop iterates to build the matrix one row at a time. - Inside the loop, a list comprehension generates all the columns for that specific row.
- Finally,
append()adds the completed row to your mainmatrix.
Advanced list techniques
Beyond basic creation, you can now reshape your data by transposing with zip(), creating jagged lists, or even leveraging the power of numpy arrays.
Transposing data with zip() into nested lists
list1 = [1, 2, 3]
list2 = [4, 5, 6]
list3 = [7, 8, 9]
transposed = list(map(list, zip(list1, list2, list3)))
print(transposed)--OUTPUT--[[1, 4, 7], [2, 5, 8], [3, 6, 9]]
Transposing data is like flipping a grid's rows and columns, and the zip() function is perfect for this. It takes multiple lists and groups their elements together based on their position.
- The
zip()function pairs the first elements from each list, then the second, and so on, creating tuples like(1, 4, 7). - Since
zip()produces tuples, you can usemap(list, ...)to convert each of these tuples into a list. - The final
list()call assembles these new lists into your transposed matrix.
Creating jagged nested lists with variable lengths
jagged_list = []
for i in range(1, 5):
jagged_list.append(list(range(i)))
print(jagged_list)--OUTPUT--[[], [0], [0, 1], [0, 1, 2]]
A jagged list is a list of lists where each inner list can have a different length. This structure is useful when your data isn't uniform. In this example, the for loop iterates with i from 1 to 4.
- In each iteration,
list(range(i))creates a new list whose length depends on the current value ofi. - The
append()method then adds this new, variable-length list to the mainjagged_list, creating the uneven or "jagged" structure.
Using numpy arrays to create nested lists
import numpy as np
array_2d = np.arange(1, 10).reshape(3, 3)
nested_list = array_2d.tolist()
print(nested_list)--OUTPUT--[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
For complex numerical tasks, the NumPy library is a game-changer. It's designed for high-performance array operations, making it perfect for creating matrices.
- The
np.arange(1, 10)function generates a sequence of numbers, which.reshape(3, 3)then organizes into a 3x3 grid. - Finally, the
.tolist()method seamlessly converts this NumPy array back into a standard Python list of lists.
This approach is especially useful when you're performing mathematical computations before needing the data in a native list format.
Move faster with Replit
Replit is an AI-powered development platform that comes with all Python dependencies pre-installed, so you can skip setup and start coding instantly. Instead of just piecing together techniques, you can use Agent 4 to build complete applications from a simple description.
The Agent takes your idea and handles the rest—from writing the code to connecting databases and deploying it live. You can go from learning about nested lists to building a real product:
- A simple inventory tracker that organizes items into a grid, with rows for products and columns for attributes like price and stock.
- A data processor that uses
zip()to transpose spreadsheet columns into rows for easier analysis in a custom dashboard. - A workout logger that creates a jagged list to store a different number of sets and reps for each exercise you complete.
Simply describe your app, and Replit will write the code, test it, and fix issues automatically, all within your browser.
Common errors and challenges
While powerful, creating nested lists can introduce subtle bugs if you're not careful with how they're initialized and accessed.
- The shallow copy trap: When you copy a list of lists, you might only be copying references, not the inner lists themselves. This means modifying an inner list in your "new" list also changes the original. To create a truly independent duplicate, you need a deep copy, which you can achieve with Python's
copy()module. - Handling an
IndexError: This error occurs when you try to access an element at a position that doesn't exist. It's a common issue with nested lists, especially jagged ones where inner lists have varying lengths. Before accessing an index, it's a good practice to check the length of the inner list to ensure your index is within bounds. - The list multiplication pitfall: Using the
*operator to create a list of lists, like[[]] * 3, is a classic trap. This doesn't create three distinct inner lists; it creates three references to the very same list. As a result, appending an element to one inner list adds it to all of them. A list comprehension is a much safer alternative for this task.
Avoiding the shallow copy trap with copy()
Using the copy() method on a nested list creates a shallow copy, meaning the outer list is new, but the inner lists are just references. Modifying an element in the copied list will unexpectedly change the original. The code below demonstrates this behavior.
original = [[1, 2], [3, 4]]
copy = original.copy()
copy[0][0] = 99
print("Original:", original)
print("Copy:", copy)
Since original.copy() only duplicates the outer list, both variables point to the same inner lists. That’s why modifying copy[0][0] also changes the original list. The code below shows how to create a fully independent copy.
import copy
original = [[1, 2], [3, 4]]
deep_copy = copy.deepcopy(original)
deep_copy[0][0] = 99
print("Original:", original)
print("Deep copy:", deep_copy)
To create a truly independent duplicate, you need the copy.deepcopy() function. It recursively copies every element, including the inner lists, so your new list is completely detached from the original. As the output shows, changing deep_copy[0][0] to 99 doesn't affect the original list. This is crucial whenever you need to modify a nested list's copy without altering the source data, such as when preparing data for separate analyses.
Handling IndexError when accessing nested lists
An IndexError is a common roadblock when you try to access an element outside a list's boundaries. With nested lists, this can happen on either the outer list or an inner one. The following code attempts to access nested_list[2][0], which will fail.
nested_list = [[1, 2], [3, 4]]
value = nested_list[2][0]
print(value)
The list nested_list has valid indices of 0 and 1. Because the code attempts to access index 2, which is out of bounds, Python raises an IndexError. See how to handle this safely in the next example.
nested_list = [[1, 2], [3, 4]]
row_index = 2
if row_index < len(nested_list):
value = nested_list[row_index][0]
print(value)
else:
print(f"Row index {row_index} out of range")
The solution is to check the list's bounds before accessing an element. By using if row_index < len(nested_list), the code confirms the index is valid, preventing the program from crashing. This defensive check is crucial, especially with jagged lists where inner list lengths are unpredictable. It allows your code to handle out-of-bounds requests gracefully instead of raising an error.
Fixing the list multiplication trap with * operator
Using the * operator to initialize a nested list seems convenient, but it's a classic pitfall. Instead of creating distinct inner lists, it creates multiple references to the same list. This means modifying one row unintentionally changes them all. The code below demonstrates this unexpected behavior.
grid = [[0] * 3] * 3
grid[0][0] = 1
print(grid)
The issue is that the * operator doesn't create three unique rows. It just copies the reference to the same inner list three times. That's why changing grid[0][0] affects every row. See the correct approach below.
grid = [[0 for _ in range(3)] for _ in range(3)]
grid[0][0] = 1
print(grid)
A list comprehension is the right tool for the job here. The expression [[0 for _ in range(3)] for _ in range(3)] builds a grid by creating a new, independent list for each row. Since the inner comprehension is re-evaluated on each pass of the outer loop, changing one row won't affect the others. As the output confirms, updating grid[0][0] only alters the first row, which is the behavior you'd expect.
Real-world applications
With the technical details and potential pitfalls covered, you can now use nested lists to build practical tools like game boards and data tables.
Representing tabular data with nested lists
Nested lists are perfect for representing tabular data, where each inner list functions as a row, making it easy to manage and analyze information like student grades.
# Create student records with [name, age, grade]
students = [["Alice", 15, 90], ["Bob", 16, 85], ["Charlie", 15, 92]]
# Calculate average grade
total_grade = sum(student[2] for student in students)
avg_grade = total_grade / len(students)
print(f"Class average: {avg_grade:.1f}")
This snippet uses a nested list to organize student records. A generator expression, (student[2] for student in students), is paired with the sum() function to efficiently calculate the total grade without creating an intermediate list in memory.
- The expression iterates through the main
studentslist. - For each inner list (
student), it accesses the grade at index2. sum()adds these grades together.
The average is then calculated by dividing this total by the number of students, determined with len().
Creating a simple game board with nested_lists
Nested lists are also a natural fit for creating grid-based games, such as a classic tic-tac-toe board.
# Initialize a 3x3 tic-tac-toe board
board = [[' ' for _ in range(3)] for _ in range(3)]
# Make some moves
board[0][0] = 'X' # Top left
board[1][1] = 'O' # Center
board[2][2] = 'X' # Bottom right
# Display the board
for row in board:
print('|'.join(row))
print('-' * 5)
A list comprehension initializes the 3x3 board, filling each cell with a space to create the grid structure. Players make moves by assigning 'X' or 'O' to specific coordinates using index access like board[0][0].
- A
forloop iterates through each row to display the current state of the game. - The
join()method formats each row into a string with|separators. - A dashed line is printed after each row to complete the classic tic-tac-toe grid visually.
Get started with Replit
Now, turn your knowledge into a real tool. Describe what you want to build to Replit Agent, like “a tool to transpose CSV data” or “a simple inventory tracker using a nested list.”
The Agent writes the code, tests for errors, and deploys your application. Start building with Replit.
Describe what you want to build, and Replit Agent writes the code, handles the infrastructure, and ships it live. Go from idea to real product, all in your browser.
Create & deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.

.png)
.png)
.png)