How to create a list of lists in Python

Learn to create a list of lists in Python. This guide covers different methods, practical tips, real-world applications, and error debugging.

How to create a list of lists in Python
Published on: 
Tue
Mar 3, 2026
Updated on: 
Wed
Mar 4, 2026
The Replit Team Logo Image
The Replit Team

A list of lists in Python is a versatile data structure that organizes complex data, like matrices or tables. It allows you to group related lists into a single, manageable variable.

In this article, you'll explore techniques to create and manage these nested structures. You will find practical tips, real-world applications, and advice to debug common errors.

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 variable nested_list is created by placing several lists inside a single pair of outer square brackets. This structure is essentially a list where each element is itself another list. Think of it as a container holding other containers.

  • The outer list acts as the primary container.
  • Each inner list, like [1, 2, 3], is a row or a distinct group of data.

This direct initialization is ideal for representing fixed, two-dimensional data—such as a matrix or game board—because its structure is defined upfront and remains easy to read.

Basic creation techniques

Moving beyond static definitions, you can create nested lists dynamically using list comprehensions or the append() method, which is perfect for structures like 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 comprehensions offer a compact and elegant way to create nested lists. The outer loop, for i in range(rows), sets up the main list's structure by iterating to create each row.

  • For each row, the inner comprehension [... for j in range(cols)] populates the columns.
  • The expression i*cols + j + 1 calculates a unique, sequential value for each cell.

This method is generally more efficient and readable than using traditional for loops with the append() method.

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]]

You can also build a nested list dynamically using a for loop. This method involves creating an empty outer_list and then populating it iteratively.

  • Inside the loop, a new inner_list is created during each pass.
  • The append() method then adds this newly created list to your outer_list.

This step-by-step approach is great when the logic for generating each row is more complex or requires multiple lines of code, giving you more granular control than a single list comprehension.

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 build a matrix by combining a for loop with a list comprehension. This method gives you the clarity of a loop for building rows while keeping the row creation logic compact.

  • The outer loop iterates three times, once for each row you want to create.
  • Inside the loop, a list comprehension generates a complete row.
  • Finally, append() adds the new row to your main matrix list.

This technique is great for when you need to perform a quick, self-contained calculation for each row before adding it to the final structure.

Advanced list techniques

Beyond basic creation, you can reshape data with zip(), create lists of varying lengths, or leverage the numpy library for more powerful matrix operations.

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]]

The zip() function is a clever way to transpose data, effectively swapping rows and columns. It works by taking multiple lists and grouping elements together based on their index.

  • The zip() function pairs the first element of each list, then the second, and so on, creating tuples like (1, 4, 7).
  • Next, map(list, ...) applies the list() constructor to each tuple, converting it into a list.
  • Wrapping the entire expression in list() assembles these new lists into your final transposed structure.

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]]

Unlike a uniform matrix, a jagged list contains inner lists of varying lengths. You can build one with a loop that dynamically changes the size of each list you append. This approach is useful when your data doesn't fit a rigid, grid-like structure.

  • The for loop iterates with the variable i taking values from 1 up to 4.
  • During each pass, list(range(i)) creates a new list whose length is determined by the current value of i.
  • Finally, append() adds this new, variably sized list to your main list.

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 more complex numerical tasks, the numpy library offers a powerful alternative. It's optimized for creating and manipulating multi-dimensional arrays—structures similar to lists of lists but with more capabilities.

  • The code first uses np.arange(1, 10).reshape(3, 3) to generate a sequence of numbers and organize them into a 3x3 grid.
  • Then, the tolist() method seamlessly converts this specialized numpy array back into a standard Python nested list.

This approach is perfect when you need numpy's performance for calculations but require a native list for compatibility with other libraries or functions.

Move faster with Replit

Replit is an AI-powered development platform that transforms natural language into working applications. Describe what you want to build, and Replit Agent creates it—complete with databases, APIs, and deployment.

For the list of lists techniques we've explored, Replit Agent can turn them into production tools:

  • Build a simple spreadsheet application that uses a nested list to manage grid data.
  • Create a data utility that transposes tabular data, swapping rows and columns using logic similar to the zip() function.
  • Deploy a dynamic form builder where each question can have a different number of answers, managed with a jagged list.

Describe your app idea, and Replit Agent will write the code, test it, and fix issues automatically. Try Replit Agent to turn your concepts into working applications in minutes.

Common errors and challenges

While powerful, nested lists come with a few unique challenges, but they’re easy to solve once you know what to look for.

Avoiding the shallow copy trap with copy()

When duplicating a nested list with the copy() method, you’re only creating a shallow copy. This means the outer list is new, but the inner lists are just references. Modifying an element in the copied list can unexpectedly change the original. See what happens in the following example.

original = [[1, 2], [3, 4]]
copy = original.copy()
copy[0][0] = 99
print("Original:", original)
print("Copy:", copy)

Because copy[0] is just a reference to original[0], modifying the element at [0][0] alters both lists. The following code shows how to fix this by creating 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)

The solution is to create a deep copy. By using copy.deepcopy(), you create a completely new, independent duplicate of the entire nested structure. This ensures both the outer and inner lists are brand new, not just references.

As a result, when you modify deep_copy[0][0], the original list remains untouched. You should always use deepcopy() when you need to modify a nested list without altering the source data.

Handling IndexError when accessing nested lists

An IndexError occurs when you try to access an element that’s out of bounds. With nested lists, this can happen if the outer list index is wrong or if an inner list's index is invalid. See what happens in the code below.

nested_list = [[1, 2], [3, 4]]
value = nested_list[2][0]
print(value)

The code tries to access nested_list[2], but the list's valid indices are only 0 and 1. Since index 2 doesn't exist, Python raises an IndexError. The corrected code below shows how to avoid this common mistake.

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")

To prevent an IndexError, you can validate an index before using it. The corrected code performs a simple check with an if statement, ensuring the row_index is less than the list's length returned by len(nested_list). This guardrail stops the program from attempting to access an element that doesn't exist. It's a crucial step when dealing with dynamically generated indices or jagged lists where inner list sizes are unpredictable.

Fixing the list multiplication trap with * operator

Using the * operator to initialize a nested list seems convenient, but it hides a common pitfall. Instead of creating independent rows, it duplicates references to the same inner list, causing changes in one row to unexpectedly affect all others.

grid = [[0] * 3] * 3
grid[0][0] = 1
print(grid)

Since [[0] * 3] creates a single list that is then referenced three times, changing grid[0][0] modifies the same underlying list that all rows share. See how to properly initialize independent rows in the code below.

grid = [[0 for _ in range(3)] for _ in range(3)]
grid[0][0] = 1
print(grid)

The solution is to use a list comprehension, which builds a grid with truly independent rows. This method avoids the reference trap by creating a new inner list during each iteration of the outer loop.

  • The outer for _ in range(3) loop builds each row one by one.
  • The inner [0 for _ in range(3)] expression generates a fresh list for every row.

This guarantees that modifying one row won't affect the others.

Real-world applications

Moving past common errors, you'll find nested lists are perfect for practical applications like organizing tabular data and creating simple game boards.

Representing tabular data with nested lists

Nested lists provide a straightforward way to manage spreadsheet-like data, where each inner list functions as a row holding a complete record.

# 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}")

In this example, the students list groups each student's data into a dedicated inner list containing their name, age, and grade. This structure keeps each record organized and self-contained.

To find the average grade, the code uses a compact generator expression, (student[2] for student in students), to pull the grade from each student's record. The sum() function then adds these grades together, and the result is divided by the total number of students, found with len(students).

Creating a simple game board with nested_lists

Nested lists are also a natural fit for building simple grid-based games, like tic-tac-toe, since each inner list can represent a row on the 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)

The code initializes a 3x3 board with a list comprehension, ensuring each row is a distinct list filled with empty spaces. Moves are placed by accessing specific cells with double-index notation, like board[0][0], and assigning a player's mark.

  • To display the board, a loop iterates through each row.
  • The join() method then combines the elements of each row into a single string, using a | character as a visual separator.

Get started with Replit

Put these concepts into practice with Replit Agent. Describe a tool like, “a matrix calculator that transposes data,” or “a utility that reads a CSV file into a nested list,” and watch it get built.

The agent writes the code, tests for errors, and handles deployment for you. It turns your ideas into fully functional applications. Start building with Replit.

Get started free

Create and 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.

Get started for free

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.