5.4.1 · D5Scientific Computing (Python)

Question bank — NumPy — ndarray structure, dtype, shape, strides

1,878 words9 min readBack to topic

Two words you must keep separate before starting:

  • buffer = the actual row of bytes in memory (the "shelf of books").
  • metadata = the small numbers that pretend the shelf is a grid (dtype, shape, strides).

Almost every trap here is someone confusing "changed the metadata" with "changed the buffer."

Warm-up: three words you'll need first

Before the traps, pin down three terms the questions lean on.

The picture below is the mental model behind almost every answer: the buffer is one shelf, and a view is just a second pair of glasses (new metadata) reading the same shelf.

Figure — NumPy — ndarray structure, dtype, shape, strides

And strides are literally the jump sizes the sigma-formula uses to hop across that shelf:

Figure — NumPy — ndarray structure, dtype, shape, strides

True or false — justify

Strides are measured in the number of elements you skip.
False. Strides are in bytes. To turn a stride into an element-step you divide by itemsize (the byte-size of one element, given by the dtype) — e.g. stride 32 with an 8-byte dtype means a 4-element jump.
reshape always creates a brand-new copy of the data.
False. When the requested layout is compatible with the current one, reshape only edits metadata and returns a view — zero bytes move. A copy happens only when the layout is impossible without reordering.
b.T physically rearranges the numbers so rows become columns in memory.
False. Transpose swaps the entries of shape and of strides; the buffer is untouched. The same bytes are now read in a different order.
Every ndarray owns its own private block of memory.
False. Views (slices, transposes, reshapes) point at a parent's buffer. Use np.shares_memory(a, b) to detect this; a.base tells you whom it borrows from.
A (3,4) array and its transpose (4,3) contain different data.
False. They contain the same twelve bytes; only the walking rule (strides) differs, so the numbers appear in a different arrangement.
All elements of an ndarray must share one dtype.
True. Homogeneity is what makes a fixed byte-step possible, so strides can be constant integers — that constant step is the whole speed story.
For a C-contiguous array the first axis has the smallest stride.
False. In C (row-major) order the last axis has the smallest stride (equal to itemsize), because neighbours along the last axis are adjacent in memory.
A 1-D array can be reported as both C-contiguous and F-contiguous at the same time.
True. With only one axis there is no row-vs-column distinction, so both contiguity flags hold simultaneously.
a.size and a.nbytes are just two names for the same number.
False. size = element count (product of shape); nbytes = size × itemsize. They coincide only when itemsize happens to be 1.
Slicing a NumPy array behaves like slicing a Python list — you get an independent copy.
False. A NumPy slice like A[1:3, :] is a view; writing into it mutates the parent. Python list slices copy; NumPy slices do not. Contrast with Python lists vs arrays.
A stride must always be a positive number.
False. A reversed slice like A[::-1] produces a negative stride: NumPy starts the offset at the last element and steps backwards through the buffer. Same bytes, walked in reverse.

Spot the error

sub = A[0:2].copy() was written to save memory versus a plain slice.
The error is the intent: .copy() uses more memory (a real duplicate). You call .copy() for independence from the parent, not to save space. Plain slicing is the memory-cheap option.
Someone claims "stride_0 = itemsize for a C-contiguous (3,4) int64 array."
Wrong axis. For C order stride_1 = itemsize = 8; stride_0 = 4 × 8 = 32. It is the last axis whose stride equals itemsize.
"A.reshape(3,4) moved element A[5] from position 5 to position (1,1) in memory."
Nothing moved in memory. The element still sits at flat byte offset 5 × itemsize; reshape only changes how indices map to that same offset. (1,1) is just a new label for flat index 5.
"After bt = b.T, editing bt is safe because it's a different object."
Being a different Python object is irrelevant. bt shares b's buffer, so bt[0,0] = 99 also changes b. Check with np.shares_memory(bt, b) before assuming safety. See NumPy — views vs copies & np.shares_memory.
"np.int8 and np.int64 differ only in speed, so I'll pick int8 to go faster."
dtype also controls range: int8 wraps past 127 (overflow), and floats lose precision. Choose dtype for correctness (range/precision) first, speed second.
"The byte offset of A[i,j] is i + j."
Missing the strides. Offset is , e.g. i·32 + j·8 for a C (·,4) int64 array — you multiply each index by its axis's byte-step, not just add indices.
"Transpose is slow because it rebuilds a (4,3) grid."
Transpose is O(1): it swaps two numbers in shape and two in strides. No grid is rebuilt; there is no real grid, only bookkeeping.
"A[::-1] had to copy the array to flip it."
No copy. Reversing sets a negative stride and moves the start pointer to the last element; the buffer is untouched and np.shares_memory(A[::-1], A) is True.

Why questions

Why does NumPy store a 2-D table as one flat buffer instead of a list of rows?
A single contiguous block is cache-friendly and lets a constant stride walk it with pure integer arithmetic — no pointer-chasing. This is the root of NumPy's speed; see CPU cache & memory locality.
Why must every element share the same dtype for strides to work?
A constant byte-step only exists if every element occupies the same number of bytes (its itemsize). Mixed sizes would break the "skip itemsize bytes" rule that strides rely on.
Why is transpose free while a genuine reordering copy is not?
Transpose reinterprets the existing bytes (swap strides), so no data is touched. A copy that changes physical order must actually read and re-write every byte — O(n) work.
Why does a transposed C-array become F-contiguous?
Swapping the strides makes the first axis hold the smallest stride (= itemsize), which is exactly the definition of column-major (Fortran) contiguity. See Row-major vs column-major (C vs Fortran order).
Why can broadcasting "stretch" an axis without allocating memory?
It sets that axis's stride to 0, so every index along it reads the same byte — a repeat with no copy. See NumPy — broadcasting.
Why is looping in Python over a big array slow even though the buffer is contiguous?
The contiguity helps the underlying C loop, but a Python-level for re-enters the interpreter per element. You want one vectorized call so the fast strided walk stays in C. See NumPy — vectorization & performance.
Why can a negative stride still be walked with the same offset formula?
Because the sum works with signed numbers: a negative simply subtracts as the index grows, moving the pointer backward from the array's start position.

Edge cases

What are the strides of a scalar np.array(7) (shape ())?
Its strides is the empty tuple (): with zero axes there are no steps to take, and there is exactly one element at offset 0.
Can reshape ever be forced to copy?
Yes — if the array's current layout can't produce the requested shape by metadata alone (e.g. reshaping a non-contiguous transposed array), NumPy silently returns a copy to make it possible.
What stride does a broadcast (size-1-stretched) axis carry?
A stride of 0, so moving along that axis returns to the same memory — the value is virtually repeated with no extra bytes.
What does a negative stride mean physically, and when do you get one?
It means "step backward through the buffer." You get it from reversed slices like A[::-1] — the view's data pointer sits at the last element and each index increment moves toward lower addresses. Still a view, no copy.
Is a 0-length array (shape (0, 4)) valid, and what is its nbytes?
Valid. size = 0 × 4 = 0, so nbytes = 0; it still carries a real dtype and shape, just no elements.
If two arrays share a buffer, does modifying one guarantee the other changes?
Only where their strided walks touch the same bytes. Sharing memory (shares_memory True) means overlap is possible; whether a specific write is seen depends on whether that byte is inside the other's window.

Connections

Each link goes deeper on a mechanism this bank only pokes at:

  • NumPy — ndarray structure, dtype, shape, strides (Hinglish) — the parent note; the full buffer + metadata story these traps quiz you on.
  • NumPy — views vs copies & np.shares_memory — how to prove whether two arrays share a buffer, the crux of the slicing traps.
  • NumPy — broadcasting — the stride-0 trick that "stretches" an axis with no copy.
  • NumPy — vectorization & performance — why the strided C-walk beats a Python for loop.
  • CPU cache & memory localitywhy one contiguous block is fast in the first place.
  • Row-major vs column-major (C vs Fortran order) — what "C-contiguous" vs "F-contiguous" really means for stride order.
  • Python lists vs arrays — the copy-on-slice behaviour that makes list intuition mislead you here.