Intuition The one core idea
Every array-creation function is a factory that stamps out a block of same-type numbers laid out in a shape , so you never have to write a slow loop to fill it. To read those functions you only need three ideas working together: what a buffer of one dtype is, what a shape describes, and how we count evenly-spaced points on a number line .
This page assumes nothing . Before you meet np.zeros or np.linspace, we build — from the ground up — every symbol, word, and picture the parent note quietly leaned on. Read top to bottom; each block earns the next.
Everything here lives on a number line — a straight horizontal ruler where left is small, right is big, and each position is one number.
Definition Number line & interval
A number line is a picture of all real numbers as points on a ruler. An interval [ a , b ] means "every number from a up to b , both ends kept." The square brackets [ ] mean included ; a round bracket ) means excluded .
Why the topic needs it: np.linspace(0, 1, 5) and np.arange(0, 1, 0.25) both place points on this line . The whole difference between them is which points and whether the right end is kept .
Recall What do the brackets mean?
[ 0 , 1 ) includes 0 but not 1 ::: left end kept, right end thrown away.
The parent note says an array is "a contiguous block of memory of the same dtype." Let's unpack every word.
Definition Buffer, contiguous, dtype
A buffer is a single unbroken strip of memory cells sitting side by side, like train carriages coupled together.
Contiguous means those cells touch — no gaps, no jumping around. This is why NumPy is fast: the computer reads neighbours in one sweep.
A dtype ("data type") is the promise that every cell holds the same kind of number using the same number of bytes — e.g. float64 (a decimal number in 8 bytes) or int64 (a whole number in 8 bytes).
The picture: compare a Python list (top row) — each cell is a pointer wandering off to a separate boxed object — with a NumPy buffer (bottom row) — the actual numbers packed in a straight line. Same numbers, wildly different layout.
Why the topic needs it: np.zeros(10**6) is fast precisely because it reserves one clean strip and writes zeros into it. And "default dtype is float64" only makes sense once you know a dtype is a fixed-width promise.
Intuition Why "same dtype" is not a limitation but a superpower
Because every cell is the same width, the computer can find cell number k by pure arithmetic: address_of_cell_0 + k * width. No searching. That single multiplication is the seed of all array speed.
For the full story see NumPy arrays — shape, dtype, ndim and Floating-point representation and rounding errors .
A buffer is just a line of numbers. Shape is how we interpret that line as a grid.
Definition Shape and the tuple that describes it
The shape is a tuple (an ordered, fixed list written in round brackets) saying how many entries lie along each direction. (3, 4) reads as "3 rows, 4 columns" = 12 numbers total.
Intuition How to say "1-D of length 3" — the
(3,) nuance
A shape is always a tuple, even in one dimension. For a plain 1-D line of 3 numbers the true shape tuple is written (3,) — the trailing comma is what makes Python treat (3,) as a one-element tuple rather than just the number 3 in ordinary brackets (in Python, (3) is simply the integer 3, because brackets around a lone value are just grouping). As a convenience , creator functions like np.zeros also accept a bare integer 3 and quietly read it as the shape (3,). So all three of these below mean the same 1-D array; only (3,) is the literal shape you will see printed by arr.shape:
np.zeros(3) → convenience: bare int accepted
np.zeros((3,)) → the explicit shape tuple
(3) → not a tuple at all, just the number 3
np.zeros(3, 4) breaks
Wrong belief: two dimensions → two arguments 3, 4.
Reality: the shape is one argument, so it must be one tuple: np.zeros((3, 4)). The second positional slot is reserved for dtype, so np.zeros(3, 4) tries to use 4 as a data type and fails.
Fix: wrap multi-D shapes in their own brackets: (3, 4).
The picture: the same 12 numbers in the flat buffer get folded into 3 rows of 4. Nothing about the memory changes — only how we picture reading it.
Recall What does the tuple
(2, 3) mean as a shape?
2 rows and 3 columns ::: a 2-D grid of 6 numbers.
Reshaping this fold without moving memory is its own topic: Reshaping — reshape, ravel, newaxis .
To fill a preallocated array (the "pro pattern" in the parent), you must point at one cell.
k counter
An index is the position number of a cell. NumPy counts from 0 : the first cell is index 0, the second is 1, and the last of N cells is index N − 1 . We write out[k] to mean "the cell at index k ." The letter k is just a counter — a name for "whichever position we're currently talking about."
Why the topic needs it: the loop for i in range(5): out[i] = i**2 walks i = 0 , 1 , 2 , 3 , 4 and drops a value into each slot. If you didn't know indexing starts at 0, the fencepost counting below would confuse you. See Array indexing and slicing .
This single idea powers both linspace and arange. It is worth a picture.
Intuition Fenceposts vs gaps
If you plant fenceposts along a fence, the number of gaps between posts is always one less than the number of posts. 5 posts → 4 gaps. This is why linspace's spacing formula divides by num − 1 , not num .
Let's earn every symbol the parent uses before we write the formulas.
a , b , num , s , Δ , x k , ⌈ ⌉
a = the start value (leftmost point on the line).
b = the stop value (the right end of the interval).
num = the number of points we ask linspace to produce (a whole number, e.g. num=5).
s = the step size we hand to arange: the fixed distance to advance each time (e.g. step=0.25).
Δ (Greek "delta") = the gap that linspace computes between two neighbouring points — one fixed spacing.
x k = the ==k -th point==, i.e. the value sitting at index k (with k = 0 the first point). So x 0 = a .
⌈ x ⌉ = the ceiling of x : round up to the next whole number. ⌈ 3.2 ⌉ = 4 , ⌈ 4.0 ⌉ = 4 .
WHAT we do: with num points and both ends kept, we have num − 1 gaps to share the total width b − a .
WHY we divide by num − 1 : because gaps = posts − 1 (the fencepost picture).
WHAT IT LOOKS LIKE: in the figure, 5 posts on [ 0 , 1 ] split the length 1 into 4 equal gaps of 0.25 .
Δ = num − 1 b − a , x k = a + k Δ , k = 0 , 1 , … , num − 1.
Here x k = a + k Δ literally means "start at a , then take k full gaps of size Δ " — so x 0 = a (no gaps yet) and x num − 1 = b (all num − 1 gaps used).
endpoint=False variant: divide by num instead
np.linspace has a switch, endpoint. By default endpoint=True and the stop b is kept , giving the num − 1 gaps above. If you set endpoint=False, the stop is dropped , and the num points now sit like the left edges of num equal slices of [ a , b ] — so there are num gaps, not num − 1 :
Δ no end = num b − a ( when e n d p o in t = F a l se ) .
Example: np.linspace(0, 1, 5, endpoint=False) gives Δ = 1/5 = 0.2 → points 0 , 0.2 , 0.4 , 0.6 , 0.8 — note 1 is absent. Why divide by num now? Dropping the right post means you no longer "spend" a post on the far end, so the same num posts create one more gap.
That is the linspace engine. For arange we instead fix the step s and ask how many points fit before we pass b :
N = ⌈ s b − a ⌉ .
Why ceiling? We keep taking steps until we would cross b ; a partial final step still counts as one more index, so we round up. And because the stop is excluded , a value landing exactly on b is dropped.
arange edge cases: sign of s , and a > b
The formula N = ⌈( b − a ) / s ⌉ quietly assumes we are moving in the right direction . Cover every case:
Positive step, a < b (normal): counts up, e.g. np.arange(0, 1, 0.25) → 4 values. N = ⌈ 1/0.25 ⌉ = 4 .
Positive step but a > b (e.g. np.arange(5, 0, 1)): b − a is negative, so ( b − a ) / s < 0 and ⌈ negative ⌉ ≤ 0 → empty array (length 0). You asked to walk up from 5 but stop below 5, so you never take a step. No error — just nothing.
Negative step, a > b (counting down, e.g. np.arange(5, 0, -1)): now both b − a and s are negative, so their ratio is positive → N = ⌈ 5/1 ⌉ = 5 , giving 5, 4, 3, 2, 1 (stop 0 excluded). This is how you build a descending range.
Negative step but a < b (e.g. np.arange(0, 5, -1)): b − a > 0 , s < 0 , ratio negative → empty array .
Zero step (step=0): division by zero → NumPy raises a ZeroDivisionError/ValueError. A step of 0 never advances, so "how many points before you pass b ?" has no finite answer.
Worked example The two formulas on
[ 0 , 1 ]
linspace(0, 1, 5): Δ = 5 − 1 1 − 0 = 0.25 → points 0 , 0.25 , 0.5 , 0.75 , 1 . Right end kept.
arange(0, 1, 0.25): N = ⌈ 0.25 1 − 0 ⌉ = 4 → points 0 , 0.25 , 0.5 , 0.75 . Right end dropped.
Same spacing, different endpoint rule — exactly the fencepost picture above.
Recall Why
num − 1 and not num in the (endpoint-kept) linspace gap?
Because num points make num − 1 gaps between them (fenceposts) ::: only the gaps get an equal share of the length.
Recall What does
np.arange(5, 0, -1) produce?
A descending range ::: 5, 4, 3, 2, 1 (stop 0 is excluded).
Definition Half-open interval
[ 0 , 1 )
rng.random() returns numbers in [ 0 , 1 ) — 0 is possible, exactly 1 is not . Same bracket convention as the number line: [ kept, ) excluded.
Intuition Why creators exist at all: no loop needed
Writing [0]*1000000 in pure Python builds a million separate pointer-objects, one at a time. np.zeros(10**6) reserves one buffer and blanks it in a single sweep. The habit of "make the whole array at once, then act on it whole" is called vectorization — the reason NumPy is fast.
Deeper in Vectorization vs Python loops and Random sampling and distributions .
Number line and intervals
Even spacing and fenceposts
Half-open interval for random
Test yourself — you are ready for the parent note when you can answer all of these out loud.
What does a round bracket ) mean in an interval like [0, 1)? The right end is excluded (0 kept, 1 not).
Why is a NumPy buffer faster than a Python list of the same numbers? It stores the numbers contiguously with one fixed dtype, no per-element pointer objects.
What is a dtype in one sentence? A promise that every cell holds the same kind of number using the same byte-width (e.g. float64).
How do you write the shape "3 rows, 4 columns" as one argument? As a tuple (3, 4).
What is the literal 1-D shape tuple for a length-3 array, and why the trailing comma? (3,); the comma makes Python treat it as a one-element tuple rather than the plain number (3).
Why does np.zeros(3, 4) fail? Shape must be one tuple; the second slot is dtype, so 4 is misread as a data type.
What index does the first element of an array have? 0 (the last of N is N−1).
For 5 points on a line, how many gaps are there between them? 4 (posts minus one).
What do the symbols num and s stand for? num = number of linspace points; s = the fixed step size for arange.
Spacing Δ for num points with both ends kept? ( b − a ) / ( num − 1 ) .
Spacing when endpoint=False? ( b − a ) / num (stop dropped, so one more gap).
What does x k denote? The k -th point, x k = a + k Δ , with x 0 = a .
What does ⌈ x ⌉ do? Rounds up to the next whole number.
Number of elements in arange(a, b, s)? ⌈( b − a ) / s ⌉ , with b excluded.
What does np.arange(0, 1, -1) return, and why? An empty array — a negative step can't reach a stop above the start.
What happens with step=0 in arange? Error (division by zero); a zero step never advances.
What does "vectorization" mean here? Making/operating on the whole array at once instead of looping element by element.
Parent topic
NumPy arrays — shape, dtype, ndim
Array indexing and slicing
Reshaping — reshape, ravel, newaxis
Vectorization vs Python loops
Random sampling and distributions
Floating-point representation and rounding errors