Views, copies and memory layout

How NumPy shares memory between arrays, what C and Fortran order really mean, and how to stop copying gigabytes by accident.

Views, copies and .base

Two arrays can point at the same bytes. When they do, one is a view of the other: writing through the view changes the original, and nothing is copied. The .base attribute tells you whether an array owns its memory.

import numpy as np

a = np.arange(6).reshape(2, 3)
v = a[0]              # a view onto row 0
v[0] = 99
a[0, 0]               # 99 - the original changed

c = a[0].copy()       # independent memory
c[0] = -1
a[0, 0]               # still 99

v.base is a           # True - v knows who owns the buffer
a.base                # None  - a owns its own data
np.shares_memory(a, v)   # True
  • Slicing and reshape give views; boolean masks and fancy indexing always give copies.
  • arr.base is the fastest way to spot an accidental view in a debugger.
  • np.shares_memory(x, y) answers the question directly for any pair of arrays.
  • A view keeps the whole original buffer alive, so a small slice of a huge array still costs the huge array.

C order, Fortran order and strides

Memory is one-dimensional; the array's shape is an interpretation layered on top of it. strides says how many bytes to step to move one position along each axis. That is why a transposed array is a view with different strides rather than rearranged data.

LayoutMeaningWhere you meet it
C_CONTIGUOUSLast axis varies fastest (row-major)Default for most arrays
F_CONTIGUOUSFirst axis varies fastest (column-major)Images and Fortran/BLAS code
Non-contiguousStrides larger than the element sizeTransposes and column slices
Zero-strideSeveral elements share one addressBroadcasting an axis of length 1
a = np.arange(6, dtype=np.int64).reshape(2, 3)
a.strides                    # (24, 8) - bytes per step along each axis
a.T.strides                  # (8, 24) - the transpose is just a view
a.T.flags["C_CONTIGUOUS"]    # False

np.ascontiguousarray(a.T)    # force a real copy into C order
np.asfortranarray(a)         # column-major copy for BLAS-friendly code
a[:, 1].strides              # (24,) - a column slice, not contiguous

Where the copies come from

m = np.ones((1000, 1000))

m.ravel()               # view when the array is contiguous
m.flatten()             # always a copy
m.T @ m                 # no copy: transposes feed BLAS directly
m.astype(np.float32)    # always a copy: the dtype changes
m[m > 0.5]              # copy: boolean mask output
np.asarray(m) is m      # True - no copy when it is already an array
np.array(m, copy=False) # NumPy 2 raises if a copy would be required
⚠️
Reshaping a non-contiguous array forces a hidden copy. a.T.reshape(-1) allocates a full duplicate, and in a tight loop that is where memory blows up. Reshape, then transpose, if the layout matters.

FAQ

How do I know if an operation copied?
Compare arr.base and np.shares_memory on the input and output. If the output has no .base and does not share memory, it owns fresh data.
Should I store my arrays in Fortran order?
Only when a downstream library (BLAS-heavy linear algebra, some image code) prefers it. Otherwise stick to C order and convert with np.ascontiguousarray at the boundary.

Performance: einsum, ufunc tricks and avoiding copies Debugging array code: shape errors and NumPy 2 pitfalls

Last refreshed 2026-09-18.