Lists of Lists
🧩 Lists of Lists (2D Lists as a Special Case)
This week's class covered "2D lists" — but the actual examples were a mix of two different things: lists that genuinely behave like a fixed grid, and lists that are just generally nested but irregular. The more accurate way to think about it: a list of lists is the general Python concept (any list containing other lists, of any lengths). A 2D list is the well-behaved special case where every inner list is the same length — that's what actually earns the name "2D," since only then does row, col indexing describe a real grid.
int[][] grid = new int[4][4] is a TRUE 2D array — rectangular by construction. You can't accidentally make one row longer than another; the shape is fixed at creation. Python has no built-in 2D array type at all — what looks like a "2D list" is really just an outer list whose elements happen to all be lists of the same length. Nothing stops you from breaking that — Python won't complain if row 3 has a different length than row 1, which is exactly the trap in this week's class material (see the Gotchas section).
[[...], [...], ...] in the JSON response with zero extra work. The same jagged-vs-rectangular distinction from this lesson matters there too: a frontend expecting a fixed grid will break if your API ever returns rows of inconsistent length.
🏗️ Building and Reading the Structure
Both examples below come from this week's actual class material.
len(li) tells you the number of rows. len(li[i]) tells you the length of row i specifically. For a true 2D list these are the same for every row — for a jagged list they're not, which is the whole distinction.[[0] * cols] * rows is broken, and use a list comprehension instead.🔄 Iterating — the Class's Approach, and a More Pythonic One
The class material used nested while loops throughout — that works, and is genuinely closer to how you'd do this in Java. Here's the same logic next to Python's more idiomatic for-based approach.
# The class's version — nested while loops
i = 0
while i < len(li):
j = 0
while j < len(li[i]):
print(li[i][j])
j += 1
i += 1
# The same thing, the Pythonic way — no index bookkeeping needed
for row in li:
for num in row:
print(num)
nums = [[10, 20], [30, 40, 50], [60]]
# The class's version — accumulate with a while loop
i = 0
count = 0
while i < len(nums):
count += len(nums[i])
i += 1
print("The number of numbers is ", count)
# The same thing, in one line
count = sum(len(row) for row in nums)
# → 6
animals = [
["Dog", "Cat", "Cow", "Lion"],
["Puppy", "Kitten", "Calf", "Cub"],
]
# The class's version — index-based while loop
index = 0
while index < len(animals[0]):
print("The mummy is a ", animals[0][index], " and the baby is a ", animals[1][index])
index += 1
# With zip() — pairs up the two rows directly, no index needed
for mummy, baby in zip(animals[0], animals[1]):
print(f"The mummy is a {mummy} and the baby is a {baby}")
📋 Quick Reference
| Operation | Syntax | Notes |
|---|---|---|
| Create (literal) | [[1,2],[3,4]] | A list of lists — rectangular ONLY if you keep row lengths consistent |
| Create a true 2D grid safely | [[0]*cols for _ in range(rows)] | List comprehension — each row is independent (see Gotchas) |
| Access an element | grid[row][col] | Outer index first, then inner |
| Number of rows | len(grid) | Outer list length |
| Length of a specific row | len(grid[i]) | Only the same as len(grid[0]) if rectangular |
| Iterate every element | for row in grid: | No index variables needed |
| Total element count | sum(len(row) for row in grid) | Works whether rectangular or jagged |
| Pair up two parallel rows | zip(row_a, row_b) | Cleaner than index-based pairing |
| Check if truly rectangular | len(set(len(r) for r in grid)) == 1 | True only if every row is the same length |
"2D list" is not enforced — it's just a convention you have to maintain yourself — unlike Java's
int[][], Python will happily let row lengths drift apart, which is exactly what happened in this week's class material with the nums example. If your code assumes every row is the same length (e.g. using len(grid[0]) as "the" row length for all rows), a jagged list will silently produce wrong results rather than an error.[[0] * cols] * rows is a classic trap — this creates ONE inner list, then repeats the same REFERENCE to it
rows times. Changing board[0][0] changes every row's first element, because they're all secretly the same list. Always build rows independently with a list comprehension: [[0]*cols for _ in range(rows)].Mixing up rows and columns —
grid[i][j] means row i, column j — easy to flip by accident, especially when porting logic from a language where you write grid[i, j] instead. There's no compiler to catch the mistake; it'll just read/write the wrong cell silently.