NumPy arrays

Why ndarray exists, how shapes and dtypes work, and how to create arrays in the ways you will actually use.

Why not just use lists?

A Python list stores pointers to objects scattered in memory. A NumPy ndarray stores fixed-size numbers in one contiguous block, and the loop that walks it runs in compiled C. That is the entire performance story: the same maths, 10–100× faster, and vastly less memory.

Propertylistndarray
Element typesMixed, any objectOne dtype for all elements
Memory layoutPointers + objectsContiguous buffer
ArithmeticLoop in PythonVectorised C loop
Element-wise a + bConcatenatesAdds element by element
Size changeCheap appendFixed size; reshape/copy instead
💡
The mental shift: think in whole arrays, not elements. If you write a Python loop over an array, you have almost certainly lost the benefit.

Shape, ndim and dtype

import numpy as np

a = np.array([1, 2, 3])            # shape (3,), dtype int64
b = np.array([[1, 2, 3], [4, 5, 6]])  # shape (2, 3)

a.shape      # (3,)
a.ndim       # 1
a.dtype      # dtype('int64')
a.size       # 3  -> total elements
b.shape      # (2, 3)  rows, columns

Shapes are read left to right as nested levels: a 2-D array is rows of columns. Confusing a shape with a length is the single most common source of indexing bugs.

np.zeros((2, 3))          # all zeros
np.ones((2, 3))           # all ones
np.full((2, 2), 7)        # filled with 7
np.eye(3)                 # identity
np.arange(0, 10, 2)       # 0 2 4 6 8
np.linspace(0, 1, 5)      # 5 evenly spaced values
np.random.default_rng(0).normal(size=(2, 2))

dtypes and precision

One dtype per array keeps memory predictable. Integers truncate silently when divided, and floats are approximate — decisions that matter in data code.

np.array([1, 2, 3], dtype=np.float64)
np.array([1.7, 2.9]).astype(np.int64)   # truncates toward zero: [1, 2]

a = np.arange(5)
a.mean()      # 2.0  - float, because mean can be fractional
a.sum()       # 10
a.max(), a.min(), a.std()
⚠️
Integer division surprises: np.array([1,2]) / 2 gives floats, but np.array([1,2], dtype=np.int64) // 2 is floor division. And mixing signed and unsigned integers can wrap around — stick to int64/float64 unless you have a reason.

FAQ

Should I use int32 to save memory?
Only when you have profiled and the array is genuinely huge. The mental cost of overflow bugs usually outweighs the saving.
What is the difference between shape (3,) and (3, 1)?
(3,) is one dimension of three items; (3, 1) is three rows of one column. Broadcasting and broadcasting errors depend on this distinction.

Indexing, slicing and masks Vectorised maths and broadcasting

Last refreshed 2026-09-18.