5.4.4 · D1Scientific Computing (Python)

Foundations — Broadcasting — rules, why it works, gotchas

2,093 words10 min readBack to topic

Before you can understand the parent note Broadcasting, you must be fluent in the words it throws around: shape, dimension, axis, size-1 axis, right-align, stride, stride-0, and the [:, None] notation. This page builds every one of them from absolute zero. Nothing below assumes you have seen NumPy before.


0. The rawest object: a strip of numbers

The single most important thing to unlearn: memory is always flat. A "2-D matrix" is a story we tell about a 1-D strip. The shape and strides are the story.

Figure — Broadcasting — rules, why it works, gotchas

Look at the figure. The bottom is the true memory: six numbers in a row. The top is how we imagine it — a grid. Same six numbers; only the instruction card differs.

See NumPy Arrays — shape, dtype, strides for the full anatomy — here we only need enough to reach broadcasting.


1. Counting: 0,1,2,3 and why indexing starts at 0

Every position along a direction is labelled by a whole number, starting at 0. If a direction has 4 items, their labels are 0,1,2,3 — see the counting note. The last label is always size minus one, never size.


2. Dimension, axis, and shape — three words for the same picture

These three words get used interchangeably in the parent note. Pin them down:

Picture Shape ndim axes
single number () 0 none
a row of 4 (4,) 1 axis 0 = along the row
grid 3 down, 4 across (3,4) 2 axis 0 = down, axis 1 = across
2 stacked grids (2,3,4) 3 axis 0 = which grid
Figure — Broadcasting — rules, why it works, gotchas

3. The special hero: a size-1 axis

Broadcasting's whole magic lives on axes whose length is exactly 1. So we must see clearly what "a direction with only one slot" looks like.

Figure — Broadcasting — rules, why it works, gotchas

The figure contrasts (3,1) (a column: 3 down, 1 across) with (1,4) (a row: 1 down, 4 across). The dashed arrows show the conceptual stretch: the single column value spreads rightward; the single row value spreads downward.

Note the two shapes below (they matter later): (3,) is a plain 1-D row of 3; (3,1) is a 2-D column. They contain the same three numbers but tell different stories — one has 1 axis, the other has 2.


4. Right-align: how two shapes are compared

The rules compare shapes by lining them up from the right. Here is why the right and not the left.

   A   ( 3 , 4 )        A   ( 3 , 1 )        A   ( 3 , 4 )
   B   ( 1 , 4 )        B   ( 1 , 4 )        B   ( 2 , 4 )
   ---------------      ---------------      ---------------
   ok:  3   4    ✓      ok:  3   4    ✓      3≠2 neither 1  ✗

The middle column of that last case is the only failure mode: two different numbers, neither of which is 1. See Reshaping and newaxis (None) indexing for how you fix a mismatch by inserting size-1 axes.


5. Stride, and the stride = 0 trick

Now the deepest word: stride. This is what makes broadcasting free.

Example: a (3,4) array of 8-byte numbers. To move one step across (axis 1, next column) you jump 8 bytes — the very next number. To move one step down (axis 0, next row) you must skip a whole row of 4 numbers = bytes. So its strides are (32, 8).

Figure — Broadcasting — rules, why it works, gotchas

Now the punchline that powers the whole topic:

That single line — "stride 0" — is why the parent note can claim broadcasting costs no extra memory. There is no clever copying; there is one pointer that refuses to advance.


6. The [:, None] notation decoded

The parent note writes w[:, None] and calls it "adds a trailing size-1 dim." Symbol by symbol:

You write Start shape Result shape Effect
w[:, None] (3,) (3,1) new size-1 axis on the right → a column
w[None, :] (3,) (1,3) new size-1 axis on the left → a row

None here is the same idea as np.newaxis — see Reshaping and newaxis (None) indexing. This is the tool for fixing the orientation trap in Example 3 of the parent note: you insert a size-1 axis so your data lines up with the axis you actually meant.


7. Why broadcasting beats writing loops (the payoff)

Everything above exists so you can write A + v instead of a hand-written loop.

This is the same win described in Vectorization vs Python loops. Broadcasting is vectorization with automatic shape-matching. The honest alternative — physically duplicating the small array with np.tile and np.repeat (explicit replication) — gives the same answer but wastes the memory that stride-0 saves.


Prerequisite map

counting 0 1 2 3

flat memory strip

shape and ndim

strides bytes to jump

axis and dimension

size-1 axis the wildcard

right-align two shapes

stride equals 0 trick

None inserts size-1 axis

Broadcasting rules

Vectorization no loops


Equipment checklist

Cover the right side and test yourself. If any one fails, re-read that section before opening the parent note.

In memory, is a 2-D array actually stored as a grid?
No — memory is always a flat 1-D strip; the grid is a story told by shape and strides.
What three things make up a NumPy array?
A flat data buffer, a shape tuple, and a strides tuple.
Why do index labels start at 0?
A label counts steps from the start; the first element needs zero steps, so it is label 0.
In shape (3,4), which number is the rows?
The first, 3 — axis 0 is the outermost/slowest axis, drawn as rows.
What is a size-1 axis and why can it stretch?
A direction holding exactly one value; since it is "the same for everyone," repeating it preserves meaning.
When comparing two shapes, from which side do you line them up?
From the right (trailing axes); pad the shorter one on the left with 1's.
What is a stride?
The number of bytes you jump in flat memory to reach the next slot along an axis.
How is a size-1 axis stretched without copying?
Its stride is set to 0, so every index along it re-reads the same byte.
What does w[:, None] do to shape (3,)?
Turns it into (3,1) by inserting a new size-1 axis on the right (a column).
What does None inside [ ] mean?
Insert a brand-new size-1 axis at that position (same as np.newaxis).
Why is A + v faster than a Python loop?
It runs a compiled C loop over flat memory and uses stride-0 to avoid any copy.