Module 2 · Lesson 4

Views vs. Copies

This is the single most important mental model in this module:

OperationView or copy?
Basic slicing (arr[1:4], grid[:, 0])View
Boolean indexing (arr[mask])Copy
Fancy indexing (arr[[0, 2]])Copy
.reshape() (when possible)View
.ravel()View when possible, else copy
.flatten()Always a copy

A view shares the same underlying memory as the array it came from — arr2 = arr[1:4] doesn’t copy any data, it just creates a new array object that looks at part of arr’s buffer. Mutating a view mutates the original. Check arr2.base is arr (or arr2.base is not None) to tell if something is a view — though .base chains can get indirect (a view of a view), so np.shares_memory(a, b) is the more robust way to ask “do these two arrays point at the same underlying memory?” directly, without worrying about how many .base hops apart they are. Call .copy() whenever you need a truly independent array.

original = np.array([1, 2, 3, 4, 5])
view = original[1:4]
print("view.base is original:", view.base is original)

view[0] = 999                 # mutating the VIEW...
print("original:", original)  # ...mutates the ORIGINAL too!

safe_copy = original[1:4].copy()
safe_copy[0] = -1
print("original after mutating the copy:", original)   # unaffected

Output:

view.base is original: True
original: [  1 999   3   4   5]
original after mutating the copy: [  1 999   3   4   5]
4.1

A function that must not mutate its input

Write double_first_n(arr, n) that returns a new array equal to arr but with its first n elements doubled. Calling this function must leave the original arr completely unchanged — a common bug is slicing (which gives a view) and then mutating in place.

💡 Hint

Start from arr.copy(), not from arr directly or a slice of it — then mutate the copy freely.

✅ Solution
def double_first_n(arr, n):
    out = arr.copy()
    out[:n] *= 2
    return out
4.2

Is it a view?

Write is_view_of(sub, original) that returns True if sub is a view into original's memory (i.e. shares its buffer), and False if sub is an independent copy. Hint: this should work for both the array returned by slicing AND the array itself (original.base is None, but it IS the data — treat that case as "not a copy", i.e. return True only when sub is genuinely backed by original's memory).

💡 Hint

np.shares_memory(sub, original) answers exactly this question and handles all the edge cases correctly — no need to reinvent it with .base.

✅ Solution
def is_view_of(sub, original):
    return bool(np.shares_memory(sub, original))