5.4.3 · D5Scientific Computing (Python)

Question bank — Indexing and slicing — basic, boolean masking, fancy indexing

1,383 words6 min readBack to topic

Before the traps, one refresher so every term below is earned:

Recall The three vocabulary words you must own
  • View ::: a new array object that points at the same numbers in memory as the original — editing one edits both.
  • Copy ::: a new array with its own fresh block of memory — editing it never touches the original.
  • Broadcast ::: NumPy stretching a smaller shape to match a bigger one so an operation lines up (see Broadcasting).

True or false — justify

Basic slicing a[1:4] always returns a copy.
False. Basic slicing returns a view — only the offset and strides change, the data buffer is shared, so it is nearly free even for huge arrays.
Boolean masking a[a > 0] returns a view into the original.
False. It returns a copy: the True elements are scattered irregularly and can't be described by one stride, so NumPy gathers them into fresh memory.
b = A[0]; b[0] = 99 leaves A unchanged.
False. A[0] is a view (basic slicing), so b shares memory with A; setting b[0] also sets A[0,0]. Use A[0].copy() for independence.
For a Python list, lst[1:3] and for a NumPy array, arr[1:3] behave the same way about copying.
False. A list slice makes a fresh copy; a NumPy slice makes a view. This mismatch is the single most common beginner bug.
a[[3,0,0,2]] can contain the same element more than once.
True. Fancy indexing reads whatever positions you list, in that order, so repeats like 0,0 legitimately duplicate an element in the result.
A boolean mask on a 2-D array preserves its 2-D shape.
False. The selection always flattens to 1-D, because the chosen cells generally do not form a rectangle.
a[a > 0] = 0 is allowed and modifies a in place.
True. Even though reading through a mask makes a copy, assigning through a mask writes back to the True positions of the original array.
The result of a[idx] where idx is a 2×2 integer array is always 1-D.
False. Fancy indexing takes the shape of the index array, so a 2×2 idx yields a 2×2 result.
A[np.ix_([0,2],[1,3])] and A[[0,2],[1,3]] return the same thing.
False. np.ix_ builds the true 2×2 block of rows {0,2} × cols {1,3}; the plain form zips the indices pointwise into just A[0,1] and A[2,3].
Using and between two array conditions works if you add parentheses.
False. Parentheses fix operator precedence but not the core issue: and needs a single boolean and raises "ambiguous truth value" on an array. You need elementwise &.
a[-1] and a[len(a)-1] select the same element.
True. Negative indices count from the end, so -1 is exactly the last element, len(a)-1.

Spot the error

A[0:2][1:3] to get rows 0–1 and columns 1–2 of a 2-D array.
Error: the second bracket slices rows of the already-sliced array, not columns. Use one bracket with a comma: A[0:2, 1:3].
a[(a < 0) & (a > -2)] written as a[a < 0 & a > -2].
Missing parentheses. & binds tighter than <, so 0 & a evaluates first and the whole thing is wrong/errors. Wrap each comparison: (a < 0) & (a > -2).
mask = a > 0; a[mask] where mask has a different length than a.
Error: a boolean mask must have the same shape as the axis it indexes (or be broadcastable), otherwise NumPy raises an index/shape error.
b = A[0] then editing b "because it's a copy of the row."
The reasoning is wrong: A[0] is basic slicing → a view. To actually copy you need b = A[0].copy().
A[[0,2],[1,3]] expecting the 4 corner values A[0,1],A[0,3],A[2,1],A[2,3].
Error in expectation: fancy index arrays are zipped pointwise, giving only A[0,1] and A[2,3]. For all four use A[np.ix_([0,2],[1,3])].
np.arange(10)[2:9:0] to step through the array.
Error: a step of 0 is illegal (it would never advance and asks for infinite elements); NumPy raises "slice step cannot be zero."

Why questions

Why does basic slicing return a view instead of a copy?
Because a range of positions can be captured by just changing the offset and strides of the same buffer — no data needs moving, so a view is both correct and instant.
Why must boolean masking and fancy indexing return copies?
The selected elements are scattered: there's no single constant stride that walks them, so NumPy has to gather them into a fresh contiguous block.
Why do we use &, |, ~ instead of and, or, not?
&/|/~ are elementwise operators that produce a whole boolean array; and/or/not demand one truth value and can't decide on an array. (See Vectorization — replacing Python loops.)
Why does a[a > 0] = 0 modify a even though a[a > 0] reads as a copy?
Assignment is a different operation: NumPy interprets the mask on the left side as "write to these True positions," so it targets the original storage directly.
Why does the result of a[idx] follow idx's shape rather than a's?
Each entry of idx is a lookup that produces one value, so the output has one value per index slot — i.e. exactly the shape of idx.
Why is A[0:2, 1:3] a rectangular block but A[[0,2],[1,3]] is not?
Slices describe independent ranges per axis (all row×col combos → a block); integer arrays are paired position-by-position (row[i] with col[i] → a diagonal-style pick).

Edge cases

What does a[5:2] return when start > stop and step > 0?
An empty array. The count formula clamped to gives elements.
What does a[::-1] do, and is it a view?
It reverses the array using step = -1, and it is still a view — reversal is expressible as a negative stride, so no copy is made.
What does an all-False mask a[a > 999] return?
An empty 1-D array (length 0) of the same dtype — no element passed the condition, so nothing is gathered.
What does an all-True mask a[a == a] return?
A flattened copy of every element in row-major order; even though all are selected, masking still flattens and copies.
What happens with an empty index list a[[]]?
You get an empty array of the same dtype; fancy indexing with zero positions selects nothing but is perfectly legal.
What does a[[10]] do on a length-5 array?
Raises an IndexError: fancy indexing bounds-checks each integer, and 10 is out of range (unlike slicing, which silently clamps).
How does a slice like a[2:100] behave when stop exceeds the length?
It's clamped to the end of the array — slicing never raises for out-of-range stops, it just stops at the last element.
What is a[:] and what does it return?
A full-range slice → a view of the whole array (shares memory). To get an independent duplicate use a.copy().

Connections

  • Indexing and slicing — basic, boolean masking, fancy indexing (index 5.4.3) (parent)
  • Views vs Copies — memory model (the view/copy backbone of every trap here)
  • NumPy arrays — shape, strides, dtype (why offset/strides make views free)
  • Broadcasting (masks and index arrays broadcasting to shape)
  • np.where and conditional selection
  • Pandas .loc / .iloc indexing