Working with Indexes in Lists of Lists
What Is a List of Lists?
A list of lists is exactly what it sounds like: a list where each element is itself a list. Think of it as a spreadsheet or a 2D grid. You access data using two indexes — the first for the row, the second for the column.
In Python, it looks like this:
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
This structure comes up constantly when dealing with tabular data, game boards, matrices, or any nested relationship in your code.
Accessing Elements: The Basics
The first index gives you the outer list element. The second index gives you the element inside that inner list.
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
print(matrix[0]) # [1, 2, 3]
print(matrix[0][0]) # 1
print(matrix[1][2]) # 6
print(matrix[2][1]) # 8
That's it. matrix[row][column]. Remember that indexes start at zero.
Negative Indexing Works Too
You can use negative indexes just like with regular lists:
print(matrix[-1]) # [7, 8, 9] — last row
print(matrix[-1][-1]) # 9 — last element of last row
Modifying Elements
You modify elements by targeting a specific index and assigning a new value:
matrix[1][1] = 99
print(matrix[1]) # [4, 99, 6]
The value you assign doesn't have to match the type of other elements. You can store anything in any cell.
Iterating Through a List of Lists
Using Nested For Loops
for row in matrix:
for element in row:
print(element, end=" ")
print()
This prints each row on its own line. Simple and readable.
Using Range With Indexes
Sometimes you need the index positions:
for i in range(len(matrix)):
for j in range(len(matrix[i])):
print(f"matrix[{i}][{j}] = {matrix[i][j]}")
Use this when you're modifying elements based on their position, not just reading values.
Using Enumerate
for i, row in enumerate(matrix):
for j, element in enumerate(row):
print(f"Position ({i}, {j}): {element}")
Enumerate gives you the index without calling len() repeatedly. Cleaner code.
Common Operations
Adding Rows or Columns
# Add a new row
matrix.append([10, 11, 12])
# Add a new element to an existing row
matrix[0].append(4)
# Insert at specific position
matrix.insert(1, [0, 0, 0])
Removing Elements
# Remove last row
matrix.pop()
# Remove specific row
del matrix[0]
# Remove element from row
matrix[0].pop(1)
Checking If an Element Exists
[5, 6] in matrix # True
5 in matrix[0] # True
10 in matrix[0] # False
The "in" operator searches only at the level you specify. It doesn't search the entire nested structure.
List of Lists vs Other Data Structures
| Structure | Access Speed | Flexibility | Best For |
|---|---|---|---|
| List of Lists | Fast (index-based) | High (mixed types, irregular shapes) | Simple grids, irregular data |
| NumPy Array | Fastest (contiguous memory) | Low (uniform type, fixed shape) | Large numerical matrices, math operations |
| Dictionary of Lists | Fast (key-based) | Medium | Named columns, sparse data |
For most general-purpose work, a list of lists is fine. Switch to NumPy when you're doing heavy numerical computation.
Getting Started: Practical Example
Let's build a simple tic-tac-toe board and check for a winner:
board = [
["X", "O", "X"],
["O", "X", "O"],
["X", " ", " "]
]
def check_winner(board):
# Check rows
for row in board:
if row[0] == row[1] == row[2] != " ":
return row[0]
# Check columns
for col in range(3):
if board[0][col] == board[1][col] == board[2][col] != " ":
return board[0][col]
# Check diagonals
if board[0][0] == board[1][1] == board[2][2] != " ":
return board[0][0]
if board[0][2] == board[1][1] == board[2][0] != " ":
return board[0][2]
return None
winner = check_winner(board)
print(f"Winner: {winner}") # Winner: X
This shows the pattern: use nested loops to scan, access via board[row][col], and return early when you find what you need.
Common Mistakes
- Confusing row and column order. matrix[row][col] is the standard, but easy to mix up when you're tired.
- Assuming uniform row lengths. Lists of lists can have ragged edges — row 0 might have 3 elements, row 1 might have 5. Check lengths before iterating if shape matters.
- Modifying while iterating. Don't add or remove elements from the list you're currently looping over. It causes index errors.
- Forgetting to initialize nested lists. Creating a list with [0] * 3 and then trying to access [0][1] crashes if you don't populate [0] first.
When to Use Something Else
A list of lists works until it doesn't. Here's when to switch:
- Need named columns? Use a dictionary of lists or a pandas DataFrame.
- Working with sparse data? Use a dictionary keyed by (row, col) tuples.
- Doing matrix math? Use NumPy. It's faster and has built-in operations.
- Need tree or graph structures? Use dedicated classes with node objects and pointers.
Lists of lists are a starting point, not a destination.