Exercises — NumPy — ndarray structure, dtype, shape, strides
Before we start, one reference picture of the two ideas we lean on most.

Level 1 — Recognition
(Can you read the metadata and say what it means?)
Problem 1.1
An array reports dtype = int64, size = 20. What is nbytes?
Recall Solution 1.1
itemsize for int64 is bytes (the "64" means 64 bits, and bytes).
What we did: multiplied element count by bytes-per-element. Why: every element is the same fixed size (homogeneity), so total bytes is just a product.
Problem 1.2
a = np.arange(6) on a default platform. State a.shape, a.ndim, a.size.
Recall Solution 1.2
arange(6) produces the flat sequence [0 1 2 3 4 5].
shape = (6,)— a 1-D array with 6 entries. (The trailing comma means "tuple with one element", not a typo.)ndim = 1— one axis.size = 6— product of the shape tuple .
Problem 1.3
For a C-contiguous float32 array of shape (5, 3), what is the stride of the last axis?
Recall Solution 1.3
float32 = 32 bits = bytes, so itemsize = 4.
The last axis in C-order always has stride equal to itemsize, because neighbours along the last axis sit next to each other in memory.
Level 2 — Application
(Plug into the offset formula and the C-stride formula.)
Problem 2.1
A = np.arange(20, dtype=np.int64).reshape(4, 5). Compute A.strides by hand, then find the byte offset and the value of A[3, 2].
Recall Solution 2.1
Strides. Shape , itemsize . Using the C-contiguous rule :
- (last axis).
- (skip one whole row of 5 elements).
Byte offset of :
Value. Divide by itemsize to get the flat element index: . Since
arangefills , the element at flat index 17 is the number 17. Cross-check with the flat-index formula ✓.
Problem 2.2
An array has shape (2, 3, 4), dtype int32. Give its C-contiguous strides.
Recall Solution 2.2
int32 → bytes. Axes are .
- .
- .
- . Why this shape of answer: to step one unit along axis 0 you must jump over an entire plane elements bytes. To step along axis 1 you jump one row of 4 elements bytes. The last axis is neighbours: 4 bytes.
Problem 2.3
b.nbytes is 96 and b.itemsize is 8. If b.shape = (d, 3, 2), find d.
Recall Solution 2.3
elements. Size is the product of the shape: .
Level 3 — Analysis
(Reason about what stays the same when metadata changes.)
Problem 3.1
b = np.arange(12).reshape(3,4) (int64). What are the shape and strides of b.T? Does b.T share memory with b?
Recall Solution 3.1
Original: shape , strides .
Transpose swaps the axes, which means it swaps both the shape entries and the stride entries:
Shares memory? Yes. Transpose is a view: not a single byte moved. np.shares_memory(b, b.T) → True.
Look at figure s02: the arrows (the strides) get their labels swapped, but the block of numbers is untouched.

Problem 3.2
After the transpose above, is b.T C-contiguous, F-contiguous, both, or neither?
Recall Solution 3.2
Rules: C-contiguous ⇔ last-axis stride and strides decrease as axis index rises. F-contiguous ⇔ first-axis stride .
b.T has strides , itemsize .
- Last-axis stride is → not C-contiguous.
- First-axis stride is → F-contiguous. ✓ So the transpose of a C-array is F-contiguous only. (A byte block written in row-major, read column-major, is column-major.)
Problem 3.3
sub = A[1:3, :] where A = np.arange(12).reshape(3,4). You run sub[0,0] = 99. What is A[1,0] afterward, and why?
Recall Solution 3.3
A[1:3, :] is a view — it points at the same buffer starting one row down. The offset of sub[0,0] is the offset of A[1,0].
has flat index ; writing there sets that byte.
Therefore A[1,0] == 99.
Why not a copy? In plain Python, list slices copy; NumPy slices view by adjusting the start pointer and shape — no bytes duplicated. Confirm with np.shares_memory(sub, A) → True.
Level 4 — Synthesis
(Combine dtype, shape, strides, views, and edge cases.)
Problem 4.1
x = np.arange(10, dtype=np.int64); y = x[::2] (every 2nd element). Give y.shape, y.strides, and whether y shares memory with x.
Recall Solution 4.1
x has strides (1-D, itemsize 8). Slicing [::2] keeps every 2nd element: it doubles the step and picks elements.
shape = (5,).strides = (16,)— step bytes to skip the element in between.- Shares memory? Yes — a strided slice is a view. No data copied; NumPy just walks with a bigger stride.
np.shares_memory(x, y)→True. Key insight: strides need not equalitemsize. Any integer step is legal, which is exactly how views with gaps work.
Problem 4.2
You have A = np.arange(6).reshape(2,3) (C-contiguous, strides (24,8)). You call A.T.reshape(6). Does this return a view or force a copy? Explain via strides.
Recall Solution 4.2
A.T has shape , strides — this is F-contiguous, not C-contiguous.
reshape(6) wants a flat 1-D array whose memory order matches the requested (default C) walk. But reading A.T in C-order (row by row of the transposed view) hops around the buffer non-contiguously — no single constant stride can describe that flat sequence.
Therefore reshape must COPY. It allocates a fresh contiguous buffer and reorders.
Check: np.shares_memory(A, A.T.reshape(6)) → False.
Contrast: A.reshape(6) (on the original C-array) is a view — reshape only copies when the requested layout is impossible on the existing strides.
Problem 4.3
Predict np.int8(100) + np.int8(50) and explain the surprising result using dtype range.
Recall Solution 4.3
int8 stores integers in the range (8 bits, signed). The true sum is above 127, so it wraps around (two's-complement overflow):
So the result is -106, dtype still int8.
Why: dtype fixes not just speed but representable range. Homogeneity means the result cell is still one int8 — it cannot magically grow to hold 150.
Fix in practice: cast up first, e.g. np.int8(100).astype(np.int16) + 50 → 150.
Level 5 — Mastery
(Design metadata / reverse-engineer / edge & degenerate cases.)
Problem 5.1
A buffer holds 12 int64 values. Someone constructs an array with shape (3,4) but strides (8, 24). Reading M[i,j], what does this array actually look like compared to the normal C-array np.arange(12).reshape(3,4)?
Recall Solution 5.1
Normal C-array: strides → moving along axis 1 (columns) steps 8 bytes = 1 element; moving along axis 0 (rows) steps 32 bytes = 4 elements. Here strides are :
- Axis 0 step bytes element.
- Axis 1 step bytes elements.
Offset in elements . So value at flat index .
That is exactly the transpose of
arange(12).reshape(4,3)— i.e. reading the same 12 bytes in column-major order. Custom strides literally re-interpret a fixed buffer. (This is whatas_strideddoes; the parent's mantra "data is sacred, metadata is cheap" in action.)
Problem 5.2
Degenerate/edge case. What is the shape, size, and nbytes of np.zeros((3, 0, 5), dtype=np.float64)? Is such an array valid?
Recall Solution 5.2
A zero-length axis is perfectly legal.
shape = (3, 0, 5).size = 3 \times 0 \times 5 = 0— empty: it contains no elements.nbytes = size × itemsize = 0 × 8 = 0bytes. The array object still exists (valid metadata, valid — empty — buffer). Indexing any element is impossible, but the shape carries real structural information (used by broadcasting and concatenation). Edge insight: the product formula handles zero automatically — no special case needed.
Problem 5.3
Reverse-engineer. An int64 array prints strides = (8, 40) and shape = (5, 3). Was this array originally built C-contiguous or F-contiguous, and how do you know?
Recall Solution 5.3
Test the definitions with :
- C-contiguous? needs last-axis stride . Here last-axis stride is . No.
- F-contiguous? needs first-axis stride . Here first-axis stride is . Yes. ✓
So this array is F-contiguous (column-major). Sanity: for F-order shape , the strides are ✓ — matches. It's likely
np.arange(15).reshape(5,3, order='F')or the.Tof a C-array of shape .
Connections
- NumPy — views vs copies & np.shares_memory — Problems 3.3, 4.1, 4.2 live here.
- Row-major vs column-major (C vs Fortran order) — Problems 3.2, 5.1, 5.3.
- NumPy — broadcasting — the "0-stride" cousin of these tricks.
- NumPy — vectorization & performance — why contiguity matters for speed.
- CPU cache & memory locality — why stride patterns change runtime.
- Python lists vs arrays — why NumPy slices view where list slices copy.
Recall One-line self-test
Given shape (a,b,c) C-contiguous itemsize s, the strides are? ::: — each axis skips the product of the sizes of all axes after it, times s.