NumPy Cheatsheet

Broadcasting

Use this NumPy reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

The Broadcasting Rule

NumPy can operate on arrays of different shapes without copying data. Before an operation, shapes are compared right-to-left. Two dimensions are compatible when they are equal or one of them is 1. A size-1 axis is stretched to match the other.

Shape A:   (3, 4)
Shape B:      (4,)   → treated as (1, 4)
Result:    (3, 4)

Shape A:   (3, 1, 4)
Shape B:      (5, 1)  → treated as (1, 5, 1)
Result:    (3, 5, 4)
import numpy as np

a = np.ones((3, 4))
b = np.array([1, 2, 3, 4])   # shape (4,) → broadcasts to (3, 4)
a + b                          # each row of a gets b added

col = np.array([[10], [20], [30]])   # shape (3, 1)
row = np.array([1, 2, 3, 4])        # shape (4,) → (1, 4)
col + row                             # (3, 4) outer sum

If shapes are incompatible, NumPy raises ValueError: operands could not be broadcast together.

Shape Compatibility Quick Reference

A shapeB shapeResult shapeCompatible?
(5,)(5,)(5,)yes
(3, 4)(4,)(3, 4)yes
(3, 4)(3,)errorno (3 ≠ 4)
(3, 4)(1, 4)(3, 4)yes
(3, 4)(3, 1)(3, 4)yes
(3, 1)(1, 4)(3, 4)yes
(2, 3, 4)(3, 4)(2, 3, 4)yes
(2, 3, 4)(3, 1)(2, 3, 4)yes
(2, 3, 4)(2, 1, 1)(2, 3, 4)yes

Common Patterns

Scalar operations

a = np.array([1, 2, 3])
a * 2      # [2, 4, 6]   — scalar is shape () → broadcasts to (3,)
a + 10     # [11, 12, 13]

Row vector added to each row

matrix = np.ones((4, 3))
bias = np.array([1, 2, 3])   # shape (3,) → same as (1, 3)
matrix + bias                  # (4, 3)  bias added to every row

Column vector added to each column

scale = np.array([1, 2, 3, 4])[:, np.newaxis]   # shape (4, 1)
matrix + scale                                     # (4, 3)  scale added to every col

Outer product

a = np.array([1, 2, 3])
b = np.array([10, 20])
a[:, np.newaxis] * b[np.newaxis, :]   # (3, 2) outer product
np.outer(a, b)                          # equivalent built-in

Outer sum / pairwise distance

x = np.array([1.0, 2.0, 3.0])
y = np.array([4.0, 5.0])
diff = x[:, np.newaxis] - y[np.newaxis, :]   # (3, 2) pairwise differences

# Euclidean pairwise distance (2-D points)
pts = np.random.rand(5, 2)
d = np.sqrt(((pts[:, np.newaxis] - pts[np.newaxis, :]) ** 2).sum(axis=-1))
# d.shape == (5, 5)

Normalizing (z-score per feature)

X = np.random.rand(100, 10)          # 100 samples, 10 features
mu = X.mean(axis=0)                   # (10,)
sigma = X.std(axis=0)                 # (10,)
X_norm = (X - mu) / sigma            # (100, 10) — mu/sigma broadcast over rows

Batch operations

# Add a per-sample bias to a batch of matrices
batch = np.ones((32, 8, 8))           # 32 matrices of shape 8×8
bias  = np.ones((32, 1, 1))           # one scalar per sample
batch + bias                           # (32, 8, 8)

Making Arrays Broadcast-Compatible

a = np.array([1, 2, 3])   # (3,)

# Insert axes to control which dimensions align
a[np.newaxis, :]   # (1, 3) — prepend axis
a[:, np.newaxis]   # (3, 1) — append axis
a[None, :]         # same as [np.newaxis, :]

np.expand_dims(a, axis=0)    # (1, 3)
np.expand_dims(a, axis=-1)   # (3, 1)

# np.broadcast_to — read-only broadcast view
np.broadcast_to(a, (4, 3))     # (4, 3) view, no copy

# np.broadcast_shapes (NumPy ≥ 1.20) — compute result shape without arrays
np.broadcast_shapes((3, 1), (1, 4), (3, 4))   # (3, 4)

np.broadcast and np.broadcast_arrays

# Iterate over broadcasted elements
b = np.broadcast(np.array([1, 2, 3]), np.array([[10], [20]]))
b.shape    # (2, 3)

# Return views that all share the same broadcasted shape
x, y = np.broadcast_arrays(np.array([1, 2, 3]), np.array([[10], [20]]))
x.shape    # (2, 3)
y.shape    # (2, 3)

Gotchas

# (3,) and (3,) → no problem
# (3, 1) and (3,) → (3, 3) ← easy mistake: shapes align from the right
# Fix: be explicit
a = np.ones((3, 1))
b = np.ones((3,))
(a + b).shape       # (3, 3)  — b treated as (1, 3), NOT (3, 1)

# Comparing two column vectors
c = np.array([[1], [2], [3]])   # (3, 1)
d = np.array([[1], [2], [3]])   # (3, 1)
(c == d).shape     # (3, 3) — broadcasts! use np.array_equal(c, d) for equality

Rule of thumb: when in doubt, add explicit np.newaxis axes rather than relying on implicit alignment. It makes intent clear and avoids silent shape bugs.