2D Arrays in Python: Nested Lists and NumPy (With Examples)

A 2D array in Python is a table of values arranged in rows and columns. Python has no built-in 2D array type, so you use either a nested list (a list of rows, like [[1, 2], [3, 4]]) or a NumPy 2D array created with np.array(), np.zeros() and similar functions. Nested lists are fine for small, mixed data; NumPy is faster and gives you column slicing and maths on whole arrays. This guide shows how to create, access, modify and display both, including the [[0] * n] * m trap and reading a 2D array from user input.

All examples were run with Python 3.12.5, NumPy 2.5.3 and Matplotlib 3.11.2 in the Windows Command Prompt; the chart is a real Matplotlib window. Reference: NumPy: the absolute basics for beginners and nested list comprehensions.

Nested list vs NumPy 2D array

import numpy as np

# 1. a nested list: a list of rows
seats = [[1, 0, 1],
         [0, 0, 1],
         [1, 1, 1]]
print(seats[1][2])            # row 1, column 2

# 2. a NumPy 2D array
grid = np.array(seats)
print(grid[1, 2], grid.shape)  # same cell, and (rows, columns)

Output:

1
1 (3, 3)
Nested listNumPy 2D array
Create[[1, 2], [3, 4]]np.array([[1, 2], [3, 4]])
One cella[1][0]a[1, 0]
A column[row[0] for row in a]a[:, 0]
Rows can differ in lengthYesNo (rectangular)
Maths on all valuesLoopsa * 2, a.sum(axis=0)
Speed / memorySlower, more memoryMuch faster, compact

Create a 2D list correctly (the [[0] * n] * m trap)

Multiplying the outer list copies references to the same inner list, so changing one row changes all of them. Use a list comprehension to build a new list for every row:

rows, cols = 3, 4

wrong = [[0] * cols] * rows            # the SAME inner list three times
wrong[0][0] = 9
print("wrong:", wrong)

right = [[0] * cols for _ in range(rows)]   # a new inner list for every row
right[0][0] = 9
print("right:", right)

print("rows share one list?", wrong[0] is wrong[1], "/", right[0] is right[1])

Output:

wrong: [[9, 0, 0, 0], [9, 0, 0, 0], [9, 0, 0, 0]]
right: [[9, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
rows share one list? True / False
Command Prompt output showing that [[0]*cols]*rows makes every row change together while a list comprehension creates independent rows
With * rows, all rows are the same list object.

Create a 2D array with NumPy

NumPy has a function for every common starting point:

import numpy as np

print(np.zeros((2, 3)))                      # all zeros
print(np.ones((2, 3), dtype=int))            # all ones, integers
print(np.full((2, 3), 7))                    # any value
print(np.arange(1, 13).reshape(3, 4))        # 1..12 as 3 rows x 4 columns
print(np.eye(3, dtype=int))                  # identity matrix
rng = np.random.default_rng(0)
print(rng.integers(1, 100, size=(2, 4)))     # random integers

Output:

[[0. 0. 0.]
 [0. 0. 0.]]
[[1 1 1]
 [1 1 1]]
[[7 7 7]
 [7 7 7]]
[[ 1  2  3  4]
 [ 5  6  7  8]
 [ 9 10 11 12]]
[[1 0 0]
 [0 1 0]
 [0 0 1]]
[[85 64 51 27]
 [31  5  8  2]]
FunctionResult
np.zeros((r, c))All zeros (float by default)
np.ones((r, c))All ones
np.full((r, c), value)Every cell set to a value
np.arange(n).reshape(r, c)A sequence arranged in rows
np.eye(n)Identity matrix
rng.integers(lo, hi, size=(r, c))Random integers

Access rows, columns and cells

NumPy uses one pair of brackets with a comma: arr[row, col]. Slices work on both axes, and axis=0 / axis=1 tell functions whether to work down the columns or across the rows:

import numpy as np

sales = np.array([[120, 135, 150],     # store A: Jan, Feb, Mar
                  [ 90, 110, 105],     # store B
                  [200, 180, 210]])    # store C

print("store B, Feb:", sales[1, 1])
print("row of store C:", sales[2])
print("all March values:", sales[:, 2])          # a column
print("first two stores, last two months:\n", sales[:2, 1:])
print("total per store:", sales.sum(axis=1))
print("total per month:", sales.sum(axis=0))

nested = sales.tolist()                           # back to a nested list
print("column from a nested list:", [row[2] for row in nested])

Output:

store B, Feb: 110
row of store C: [200 180 210]
all March values: [150 105 210]
first two stores, last two months:
 [[135 150]
 [110 105]]
total per store: [405 305 590]
total per month: [410 425 465]
column from a nested list: [150, 105, 210]
Command Prompt output of NumPy 2D array indexing: one cell, a row, a column, a sub-array slice and sums per row and per column
Cells, rows, columns and slices of a 2D sales array.

Add rows and columns and update values

import numpy as np

matrix = [[1, 2], [3, 4]]
matrix.append([5, 6])                         # add a row to a nested list
for row in matrix:
    row.append(0)                             # add a column
print(matrix)

arr = np.array([[1, 2], [3, 4]])
arr = np.vstack([arr, [5, 6]])                # add a row (returns a new array)
arr = np.hstack([arr, np.zeros((3, 1), dtype=int)])   # add a column
arr[0, 0] = 99                                # update one cell
print(arr)
print("transposed:\n", arr.T)

Output:

[[1, 2, 0], [3, 4, 0], [5, 6, 0]]
[[99  2  0]
 [ 3  4  0]
 [ 5  6  0]]
transposed:
 [[99  3  5]
 [ 2  4  6]
 [ 0  0  0]]

Lists change in place with append(). NumPy arrays have a fixed size, so np.vstack(), np.hstack() and np.append() return a new array; when you add many rows, collect them in a list first and convert once at the end.

Create a 2D array from user input

rows = int(input("Rows: "))
cols = int(input("Columns: "))

matrix = []
for r in range(rows):
    values = input(f"Row {r + 1} ({cols} numbers separated by spaces): ").split()
    matrix.append([int(v) for v in values[:cols]])

print("Your 2D array:")
for row in matrix:
    print(row)

Output (the user typed 2, 3 and two rows of numbers):

Rows: 2
Columns: 3
Row 1 (3 numbers separated by spaces): 4 5 6
Row 2 (3 numbers separated by spaces): 7 8 9
Your 2D array:
[4, 5, 6]
[7, 8, 9]
Command Prompt running a Python program that asks for rows and columns and reads each row of numbers typed by the user into a 2D list
Rows and values typed in the Command Prompt.

For a NumPy array, wrap the result: np.array(matrix).

Nested list vs NumPy: speed

import time
import numpy as np

n = 1000
nested = [[i * n + j for j in range(n)] for i in range(n)]
arr = np.array(nested)

start = time.perf_counter()
total = sum(sum(row) for row in nested)
t_list = time.perf_counter() - start

start = time.perf_counter()
total_np = arr.sum()
t_np = time.perf_counter() - start

print(f"nested list sum: {total}  in {t_list * 1000:.1f} ms")
print(f"NumPy array sum: {total_np}  in {t_np * 1000:.1f} ms")
print(f"memory: list of lists ~{sum(map(len, nested)) * 28 // 1_000_000} MB of int objects, NumPy {arr.nbytes // 1_000_000} MB")

Output (one run; timings differ between computers):

nested list sum: 499999500000  in 4.7 ms
NumPy array sum: 499999500000  in 0.7 ms
memory: list of lists ~28 MB of int objects, NumPy 8 MB
Command Prompt timing comparison of summing a 1000 by 1000 nested list and a NumPy 2D array
Summing one million values: NumPy works on the whole block of memory at once.

Display a 2D array as a grid

imshow() draws each cell as a coloured square, which makes patterns in a 2D array easy to see:

import matplotlib.pyplot as plt
import numpy as np

sales = np.array([[120, 135, 150, 170],
                  [ 90, 110, 105, 125],
                  [200, 180, 210, 240]])

fig, ax = plt.subplots(figsize=(6, 3.8))
im = ax.imshow(sales, cmap="YlGn")                       # show the 2D array as a grid
ax.set_xticks(range(4), ["Jan", "Feb", "Mar", "Apr"])
ax.set_yticks(range(3), ["Store A", "Store B", "Store C"])
for (r, c), value in np.ndenumerate(sales):
    ax.text(c, r, value, ha="center", va="center", color="white" if value > 190 else "black")
fig.colorbar(im, ax=ax, label="Sales")
plt.tight_layout()
plt.show()
Matplotlib imshow heat map of a NumPy 2D sales array with store names on the y-axis, months on the x-axis and the value in each cell
A 3×4 NumPy array shown with imshow().

Save and load a 2D array

import numpy as np

arr = np.arange(12).reshape(3, 4)
np.savetxt("grid.csv", arr, delimiter=",", fmt="%d")   # readable text file
loaded = np.loadtxt("grid.csv", delimiter=",", dtype=int)
np.save("grid.npy", arr)                               # fast binary file
print(np.array_equal(arr, np.load("grid.npy")))

Related Python array and NumPy tutorials:

Frequently asked questions

How do I create a 2D array in Python?

Use a nested list, [[0] * cols for _ in range(rows)], or NumPy, np.zeros((rows, cols)).

Does Python have a built-in 2D array?

No. The standard approaches are a list of lists or a NumPy ndarray with two dimensions.

Why does [[0] * cols] * rows give wrong results?

It repeats a reference to the same inner list, so all rows change together. Build each row separately with a list comprehension.

How do I get a column from a 2D array?

NumPy: arr[:, col]. Nested list: [row[col] for row in matrix].

How do I find the number of rows and columns?

NumPy: rows, cols = arr.shape. Nested list: len(matrix) and len(matrix[0]).

Should I use a nested list or NumPy?

Use NumPy for numbers and anything larger than a small table; use nested lists for small grids or mixed data types.