Why NumPy Boolean Masking Creates Copies, Not Views

In Python's NumPy library, extracting data using boolean masking always creates a new independent array copy rather than a memory-sharing view. This occurs because NumPy views rely on a rigid internal metadata structure consisting of a base memory buffer, shape, and fixed byte offsets called strides. Because boolean conditions frequently select arbitrary, non-contiguous elements that lack a regular step size, NumPy cannot represent the resulting data using fixed strides alone. Consequently, the library must extract the matching elements and allocate them into a brand-new contiguous block of memory.

The Mechanism of NumPy Views

To understand why boolean masking produces copies, it is essential to understand how NumPy achieves views through basic slicing. A NumPy array (ndarray) consists of two main components:

  1. A contiguous or semi-contiguous block of raw data stored in memory.
  2. An array header containing metadata: data type (dtype), dimensions (shape), and strides.

Strides dictate how many bytes a program must skip in memory to jump to the next element along any given axis. When you slice an array using standard integer step syntax—such as arr[::2]—NumPy creates a view by simply creating a new header with modified strides, pointing directly to the original memory buffer. Because the spacing between every selected item is identical and predictable, no data needs to be duplicated.

The Irregularity of Boolean Indexing

Boolean masking falls under what NumPy categorizes as "advanced indexing." When applying a mask like arr[arr > 5], the filter depends on the values of the data rather than their structural positions.

Consider an array containing values where the mask evaluates to True at indices 0, 3, 4, and 9. The byte distances between these elements are inconsistent: three steps, then one step, then five steps.

Because an ndarray view can only use a single fixed stride value per dimension, it is mathematically impossible to construct a single view that maps directly onto these scattered memory locations. To provide an array of the selected values, NumPy must iterate over the data, read the matching items, and write them sequentially into an entirely new memory buffer.

Advanced Indexing Rules

NumPy strictly separates its indexing mechanisms into two categories:

Even if a boolean mask happens to select elements that are uniformly spaced—such as a mask alternating strictly between True and False—NumPy's internal dispatch mechanism treats all advanced indexing identically. The runtime does not inspect the boolean array to check if a uniform stride is theoretically possible; it unconditionally executes the copy-generating code path.

In-Place Assignment Exception

A common point of confusion arises when modifying arrays using masks, such as arr[mask] = 0. While extracting subsets via sub = arr[mask] returns a copy, direct assignment modifies the original array in place.

This works because Python maps assignment to the __setitem__ method rather than __getitem__. NumPy interprets the mask, locates the corresponding positions in the original memory buffer, and overwrites the values directly without ever creating an intermediate view or returning an extracted array.