Coding for ML

Views, Copies & Memory in NumPy

Understanding contiguous array memory layouts, stride tricks, array views, and explicit copies to avoid silent data mutation bugs.

🟡 intermediate5 min readcodingnumpy
Views, Copies, and Memory Layout in NumPy govern how array data sits in system RAM memory. Array Views share underlying data memory buffers with original parent arrays, providing fast slicing without memory duplication. Modifying a View mutates original array values silently. Engineers use explicit copies, memory contiguous checks, and C vs Fortran order layouts to write high performance, bug free machine learning code.

Underlying Memory Architecture of NumPy

A NumPy array consists of two parts:

  1. Raw Data Buffer: A continuous block of memory bytes storing numbers.
  2. Array Metadata (Header): Shape tuple, Data type (dtype), Strides, and Memory Flags.
┌─────────────────────────────────────────────────────────────┐
│ ARRAY METADATA: Shape=(2,3), dtype=int64, Strides=(24,8)   │
├─────────────────────────────────────────────────────────────┤
│ RAW MEMORY BUFFER: [ 10 | 20 | 30 | 40 | 50 | 60 ]         │
└──────────────────────────┬──────────────────────────────────┘
                           │
                           ▼
              Shared Memory Buffer!

Array Views vs Array Copies

┌──────────────────────────┬──────────────────────────┐
│ ARRAY VIEW               │ ARRAY COPY               │
├──────────────────────────┼──────────────────────────┤
│ Shares SAME memory buffer│ Allocates BRAND NEW      │
│ as parent array!         │ independent memory buffer│
│ Created by basic slicing │ Created by arr.copy() or │
│ (arr[0:5]) or reshape(). │ fancy indexing.          │
│ Modifying view MUTATES   │ Modifying copy DOES NOT  │
│ parent array! Fast, zero │ mutate parent array!     │
│ extra memory!            │ Takes extra RAM!         │
└──────────────────────────┴──────────────────────────┘

The Silent Mutation Trap

import numpy as np

parent = np.array([10, 20, 30, 40, 50])
view_slice = parent[0:3]  # Basic slicing creates a VIEW!

view_slice[0] = 999  # Mutate the view...

print(parent)
# Output: [999, 20, 30, 40, 50]  <── PARENT ARRAY WAS MUTATED SILENTLY!

To isolate modifications safely, call .copy() explicitly:

safe_copy = parent[0:3].copy()  # Independent memory buffer

Strides and Memory Contiguity

Strides define how many bytes to step in RAM memory to move to the next element along each dimension:

Transposing an array (arr.T) swaps strides without moving data in memory. This makes the transposed array non-contiguous, which can slow down GPU operations!

Fix non-contiguous arrays using np.ascontiguousarray(arr).

Checking Array Memory Status

arr = np.ones((3, 3))
print(arr.flags.c_contiguous)  # True
print(arr.base is None)  # True (Owns its memory!)

transposed = arr.T
print(transposed.flags.c_contiguous)  # False (Non-contiguous view!)
print(transposed.base)  # Points to original arr memory!

Say this out loud

NumPy arrays separate metadata from raw memory buffers. Basic slicing and reshaping create array views sharing the parent memory buffer, meaning mutations affect parent arrays silently. Explicit .copy() calls allocate independent memory. Non-contiguous arrays created by transpositions should be converted using ascontiguousarray for fast memory access.

Followups to expect

  1. What is Fancy Indexing in NumPy? Indexing arrays using integer arrays or boolean masks (arr[arr > 5]), which always returns a new Copy rather than a View.
  2. How to check memory size of a NumPy array? Calculate total memory using arr.nbytes (or arr.size * arr.itemsize).

Check yourself

Question 1 of 3

What happens to the original parent NumPy array when you modify elements in a basic slice View (e.g. sub = arr[0:5])?

More in Coding for ML

See all →
Implement Linear Regression from Scratch5 minImplement Self-Attention from Scratch5 minNumPy Broadcasting & Vectorization5 min