Computer Programming 1 (Python) • Core Data Structures

2D Lists — I can represent 2D data as a list of lists and process it with nested loops at my level.

Learn to organize grid-like data in Python using nested lists and traverse every element with nested loops.

A Python coding terminal with a program flow and laptop.
Inside this lesson 10 sections 5 questions 8 figures

SECTION 1

Historical Context & Motivation

Since the earliest days of computing, programmers have needed ways to represent information that is naturally organized into rows and columns. Spreadsheets, game boards, images, and seating charts all share a common trait: their data lives on a two-dimensional grid. A single flat list can hold a sequence of values, but it falls short when you need to express the idea that certain values belong to the same row while others belong to the same column. This gap in expressive power led programmers to nest one list inside another, creating what we now call a 2D list (also known as a two-dimensional array or matrix).

1950s • Fortran Introduces Multi-Dimensional Arrays

Fortran, one of the first high-level programming languages, introduced built-in support for multi-dimensional arrays. Scientists used them to represent matrices in physics and engineering calculations.

1970s • C Language and Row-Major Storage

The C programming language stored 2D arrays in row-major order in memory, establishing conventions that many later languages would follow. This made nested-loop traversal a standard pattern.

1991 • Python Is Created

Guido van Rossum released Python, which represents 2D data as a list of lists rather than a rigid fixed-size array. This flexible approach makes it easy to build and modify grids on the fly.

2000s–Now • 2D Structures Everywhere

From NumPy arrays for data science to pixel grids in image processing and tile maps in game development, 2D data structures remain one of the most commonly used patterns in all of programming.

The central question this lesson addresses is straightforward: How do we store, access, and process data that is organized in rows and columns using Python's list-of-lists structure and nested for-loops? By the end, you will be able to create 2D lists, read individual cells, and loop through every element systematically.

SECTION 2

Core Principles & Definitions

Before writing any code, let's lock down the key ideas. A 1D list (one-dimensional list) is a single sequence of values, like [10, 20, 30]. A 2D list is a list whose elements are themselves lists. Each inner list represents one row of the grid. When every inner list has the same length, the 2D list looks exactly like a table with uniform rows and columns.

1

Outer List = Rows

The outer list holds all the rows. Its length tells you how many rows the grid has. You access a specific row using a single index, like grid[0] for the first row.
2

Inner List = Columns

Each inner list contains the column values for that row. The length of an inner list tells you how many columns exist. Access a specific cell with two indices: grid[row][col].
3

Nested Loops for Traversal

An outer loop iterates through each row. An inner loop iterates through each element within that row. Together they visit every cell exactly once—this pattern is called nested iteration.
4

Zero-Based Indexing

Like all Python lists, 2D lists use zero-based indexing. The top-left element is grid[0][0] and the bottom-right is grid[rows−1][cols−1].
✦

KEY TAKEAWAY

Think of a 2D list like a parking garage. The outer list is the list of floors in the garage. Each floor (inner list) has its own row of parking spaces (elements). To find a specific car, you first pick the floor number (row index), then walk to the right space number (column index). A nested loop is like an attendant who visits every single floor and every space on that floor—ensuring no car is missed.

SECTION 3

Visual Explanation — The Grid Model

The diagram below shows a 3×4 grid stored as a Python list of lists. The outer list contains three inner lists (rows 0, 1, and 2). Each inner list contains four elements (columns 0 through 3). On the left you see the Python syntax; on the right you see the same data laid out as a visual grid with row and column indices labeled.

2D List: Python Syntax ↔ Grid VisualizationPython Codegrid = [[10, 20, 30, 40], # row 0[50, 60, 70, 80], # row 1[90, 100, 110, 120] # row 2]grid[1][2] → 70 ↑ row 1 ↑ col 2len(grid) → 3 (rows)len(grid[0]) → 4 (cols)Grid Viewcol 0col 1col 2col 3row 0row 1row 2102030405060708090100110120grid[1][2] = 70
The left panel shows the Python code that creates a 3×4 grid. The right panel maps the same data onto a visual table. The dashed cyan box highlights the cell at grid[1][2], which contains 70 (row 1, column 2).

Notice how the first index selects the row and the second index selects the column within that row. This two-step lookup mirrors how you find a seat in a movie theater: first you find your row, then you count across to the correct seat number. The function len(grid) returns the number of rows (3), while len(grid[0]) returns the number of columns in the first row (4).

SECTION 4

How Nested Loops Process a 2D List

The most common operation on a 2D list is visiting every element. This is accomplished with a nested loop: an outer loop that iterates over each row, and an inner loop that iterates over each element in that row. There are two flavors you should know—index-based and value-based traversal.

Index-Based Traversal

INDEX-BASED PATTERN

forrinrange(len(grid)):forcinrange(len(grid[r])):process(grid[r][c])for r in range(len(grid)): for c in range(len(grid[r])): process(grid[r][c])

r = row index (0 to number of rows − 1), c = column index (0 to number of columns in that row − 1). Use this pattern when you need to know the position of each element.

Value-Based Traversal

VALUE-BASED PATTERN

forrowingrid:forvalueinrow:process(value)for row in grid: for value in row: process(value)

row = the entire inner list for that row, value = the individual element. This pattern is simpler when you only care about the values, not their positions.

Total Iterations

TOTAL CELL VISITS

totalvisits=rows×columnstotal visits = rows × columns

For a grid with 3 rows and 4 columns the inner statement executes 3 × 4 = 12 times. In general, for an m × n grid, the loop body runs m × n times.

⚠

Common Mistake

Swapping the row and column indices is one of the most common bugs. If you write grid[c][r] instead of grid[r][c], you'll get an IndexError whenever the grid isn't square (same number of rows and columns). Always remember: row first, column second.

SECTION 5

Common 2D List Patterns

Beyond simple traversal, several recurring patterns appear when working with 2D lists. The diagram below illustrates four of the most important: summing all elements, finding a maximum value, processing a single row, and processing a single column. Each pattern adapts the nested-loop template in a slightly different way.

Four Common 2D List Patterns① Sum All Elementstotal = 0for row in grid: for val in row: total += valAccumulator pattern applied toevery cell in the grid.② Find Maximummax_val = grid[0][0]for row in grid: for val in row: if val > max_val: max_val = valTrack and update the largest value seen.③ Process One Rowtarget_row = 1row_sum = 0for val in grid[target_row]: row_sum += valOnly one loop needed—select the row,then iterate through its elements.④ Process One Columntarget_col = 2col_sum = 0for row in grid: col_sum += row[target_col]Loop through rows, but pick the samecolumn index from each row.
Four essential patterns for 2D list processing. Patterns ① and ② require full nested loops. Pattern ③ uses a single loop on one row. Pattern ④ uses a single loop that extracts the same column from every row.
PatternLoops NeededWhen to Use
Sum / Count / Average all cellsNested (2 loops)When the result depends on every element in the grid.
Find max / minNested (2 loops)When searching the entire grid for an extreme value.
Process one rowSingle loopWhen the task targets a specific row only.
Process one columnSingle loopWhen the task targets a specific column across all rows.
Build a 2D listNested (2 loops)When creating a grid dynamically using input or calculations.

Common 2D list operations and the loop structures they require.

SECTION 6

Worked Example — Classroom Grade Averages

Suppose you have a 2D list where each row stores one student's test scores across four exams. You want to compute each student's average and then find the overall class average. Let's walk through the solution step by step.

Computing Student and Class Averages

  1. 1

    Step 1 — Define the 2D list

    We store three students' scores as a list of lists. Each inner list holds four exam scores: scores = [[85, 92, 78, 90], [70, 65, 80, 75], [95, 88, 92, 97]]
    3 rows (students) × 4 columns (exams)
  2. 2

    Step 2 — Loop through each row to find student averages

    We use an index-based outer loop so we can label each student. Inside, we sum the row and divide by the number of exams: for r in range(len(scores)): total = 0 for c in range(len(scores[r])): total += scores[r][c] avg = total / len(scores[r]) print(f"Student {r}: {avg}")
    Student 0: 86.25, Student 1: 72.5, Student 2: 93.0
  3. 3

    Step 3 — Compute the class average

    To find the overall class average, sum every cell in the grid and divide by the total number of cells: grand_total = 0 for row in scores: for val in row: grand_total += val num_cells = len(scores) * len(scores[0]) class_avg = grand_total / num_cells
    grand_total = 1007, num_cells = 12, class_avg ≈ 83.92
  4. 4

    Step 4 — Verify the result

    A quick sanity check: the individual averages were 86.25, 72.5, and 93.0. Their average is (86.25 + 72.5 + 93.0) / 3 ≈ 83.92, which matches. This confirms our nested loop processed every cell correctly.
    Class average = 83.92 ✓

SECTION 7

Strengths & Limitations of 2D Lists

Python's list-of-lists approach to 2D data is wonderfully flexible, but it is not the ideal choice in every situation. Understanding its strengths and limitations helps you decide when to use it and when to explore alternatives.

StrengthsLimitations
Easy to create and modify — just use familiar list operations like append and indexing.No built-in enforcement that all rows have the same length, which can lead to 'ragged' lists and subtle bugs.
Can hold mixed data types — one cell can be an int, another a string.Slower for large numeric computations compared to optimized libraries like NumPy.
Great for representing game boards, seating charts, simple pixel grids, and tabular data.No built-in matrix operations (transpose, matrix multiplication) — you must code them yourself.
Rows can be different lengths, which is useful for triangular or irregular data.For very large datasets, memory usage is higher than fixed-type arrays.

Comparing the strengths and limitations of Python's list-of-lists for 2D data.

💡

WHEN TO USE 2D LISTS

For most tasks in an introductory Python course — tic-tac-toe boards, gradebooks, simple image filters — a list of lists is the right tool. It keeps your code readable and avoids extra library dependencies. Once you start working with large numeric datasets or need operations like matrix multiplication, you'll graduate to libraries like NumPy, which is built on the same conceptual foundation but runs much faster.

SECTION 8

Connection to Advanced Structures

The 2D list concept you are learning today is the gateway to several more powerful tools that you will encounter as your programming journey continues. The table below maps what you know now to what you'll learn next.

What You Know NowWhat Comes Next
2D list of lists in pure PythonNumPy 2D arrays — fixed-type, fast, with built-in math operations like dot products and transpositions.
Nested for-loops to visit every cellVectorized operations — NumPy lets you add, multiply, or compare entire arrays without writing any loops.
Grid as rows and columns of numbersPandas DataFrames — labeled rows and columns, making tabular data analysis intuitive.
Flat, in-memory storageDatabases and SQL — store millions of rows in tables on disk and query them efficiently.

How 2D lists connect to more advanced data structures and tools.

The skills you build now — thinking in rows and columns, using nested loops, and selecting specific cells with double indexing — will directly transfer to every one of these advanced tools. Mastering the fundamentals makes the jump to professional libraries feel natural rather than overwhelming.

SECTION 9

Practice Problems

Question 1: CONCEPTUAL

Given the 2D list m = [[5, 10], [15, 20], [25, 30]], what does m[2][0] evaluate to? Explain your reasoning in terms of rows and columns.
Show answer
The expression m[2][0] evaluates to 25. The first index, 2, selects the third inner list [25, 30] (row 2, since indexing starts at 0). The second index, 0, selects the first element of that list, which is 25. Think of it as going to row 2 and then picking column 0.

Question 2: BASIC CALCULATION

Write a nested loop that prints every element of the following 2D list, one value per line: data = [[1, 2, 3], [4, 5, 6]]
Show answer
Use a value-based nested loop: for row in data: iterates over each inner list, and for val in row: iterates over each element within that list. Inside the inner loop, print(val) outputs the value. The output will be 1, 2, 3, 4, 5, 6, each on its own line. The outer loop runs 2 times (one per row) and the inner loop runs 3 times per row, producing 6 prints total.

Question 3: INTERMEDIATE

Given temps = [[72, 75, 78], [68, 70, 73], [80, 82, 85], [65, 63, 67]] where each row represents one day's morning, afternoon, and evening temperatures, write code that calculates and prints the average temperature for each day.
Show answer
Use an index-based outer loop with for d in range(len(temps)): to track which day you're on. Inside, initialize total = 0 and use for t in temps[d]: to add each reading to total. After the inner loop, compute avg = total / len(temps[d]) and print it. Day 0 average = (72+75+78)/3 = 75.0, Day 1 = 70.33, Day 2 = 82.33, Day 3 = 65.0. This combines the accumulator pattern with row-level processing.

Question 4: APPLIED

You are building a tic-tac-toe game. The board is represented as: board = [["X", "O", "X"], ["O", "X", " "], [" ", "O", "X"]] Write a function count_marks(board, mark) that returns how many times a given mark ("X", "O", or " ") appears on the board.
Show answer
Define the function with a counter initialized to 0. Use a nested loop to check every cell: for row in board: for cell in row: if cell == mark: count += 1. Return count at the end. Calling count_marks(board, "X") returns 4 because X appears at positions [0][0], [0][2], [1][1], and [2][2]. This is the count/accumulate pattern adapted to non-numeric data, showing that 2D lists can hold strings, not just numbers.

Question 5: CRITICAL THINKING

A teacher stores quiz scores in grades = [[88, 92, 79], [95, 85, 100], [70, 78, 82]] where each row is a student and each column is a quiz. Write code that finds the highest average quiz score across all quizzes (i.e., compute the average of each column and return the maximum). Which column has the highest average?
Show answer
This requires processing columns rather than rows. For each column index c from 0 to len(grades[0]) − 1, loop through all rows and sum grades[r][c], then divide by the number of rows. Column 0 average = (88+95+70)/3 = 84.33, Column 1 = (92+85+78)/3 = 85.0, Column 2 = (79+100+82)/3 = 87.0. The maximum column average is 87.0, belonging to quiz 2 (column index 2). This problem synthesizes column traversal with the max-tracking pattern, requiring you to manage an outer loop over columns and an inner loop over rows — the reverse of the typical row-first pattern.

SUMMARY

Lesson Summary

Summary

A 2D list in Python is a list of lists where the outer list holds rows and each inner list holds the column values for that row. You access a specific cell using double indexing: grid[row][col]. To process every element, use a nested loop — an outer loop for rows and an inner loop for columns. The total number of iterations equals rows × columns.

Key patterns include summing all elements, finding a maximum, processing a single row with one loop, and processing a single column by looping through rows while keeping the column index fixed. You can choose between index-based traversal (using range(len(...))) when you need positions, or value-based traversal (using for item in list) when you only need values. These foundational skills prepare you for advanced tools like NumPy arrays and Pandas DataFrames.

Varsity Tutors • Computer Programming 1 (Python) • 2D Lists