Copies vs Views
Reviewed & published by Brayan K
A view is a NumPy array that shares the same memory as another array, while a copy is an independent array with its own memory — and knowing which one you have is the key to avoiding accidental data corruption.
Part of the free NumPy course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
In this lesson you'll see how slicing returns a memory-sharing view, how .copy() breaks that link, how to inspect the .base attribute, and why fancy and boolean indexing always return copies.
What You'll Learn in This Lesson
- Why slicing returns a view that shares memory
- The classic gotcha: editing a slice changes the original
- Making an independent array with .copy()
- Inspecting the .base attribute
- Why fancy and boolean indexing return copies
- When reshape returns a view
1 Slicing Returns a View
When you slice a NumPy array with arr[1:4], you do not get a fresh copy of the data. You get a view — a lightweight window onto the same underlying memory. This is fast and saves memory, but it has a surprising consequence: writing through the view changes the original.
import numpy as np
original = np.array([10, 20, 30, 40, 50])
# A slice is a VIEW into the same memory
view = original[1:4]
print(view) # [20 30 40]
# Change the view...
view[0] = 999
# ...and the ORIGINAL changes too!
print(view) # [999 30 40]
print(original) # [ 10 999 30 40 50]
# ✅ Expected output:
# [20 30 40]
# [999 30 40]
# [ 10 999 30 40 50]Expected output: the view [20 30 40], then after assignment the view is [999 30 40] and the original becomes [10 999 30 40 50].
2 The Classic Gotcha
This view behavior is the single most common source of confusing bugs for NumPy beginners. You pull out a slice, modify it thinking it is separate, and silently mutate your source data.
import numpy as np
data = np.array([[1, 2, 3],
[4, 5, 6]])
# Grab the first row — this is a VIEW, not a copy
first_row = data[0]
# "Reset" the row to zeros
first_row[:] = 0
# The original matrix was mutated!
print(data)
# [[0 0 0]
# [4 5 6]]
# ✅ Expected output:
# [[0 0 0]
# [4 5 6]]Expected output: the original matrix is now [[0 0 0] [4 5 6]] — zeroing the row view changed the source.
3 .copy() Makes It Independent
The fix is simple: call .copy() to allocate fresh memory. A copy is fully independent — editing it never touches the original. The .base attribute lets you confirm which kind you have: a view's .base points back to its parent, while a copy's .base is None.
import numpy as np
original = np.array([10, 20, 30, 40, 50])
# Make an independent copy of a slice
safe = original[1:4].copy()
safe[0] = 999
print(safe) # [999 30 40]
print(original) # [10 20 30 40 50] -> unchanged!
# .base tells you what an array shares memory with
view = original[1:4]
print(view.base is original) # True -> it's a view
print(safe.base) # None -> it's a copy
# ✅ Expected output:
# [999 30 40]
# [10 20 30 40 50]
# True
# NoneExpected output: [999 30 40] for the copy, the original unchanged as [10 20 30 40 50], then True for the view's base and None for the copy's base.
4 Fancy & Boolean Indexing Return Copies
Not every selection shares memory. Fancy indexing (using a list of integer positions) and boolean indexing (using a True/False mask) always return a copy. Meanwhile, reshape usually returns a view because it just reinterprets the same data with a new shape.
import numpy as np
arr = np.array([10, 20, 30, 40, 50])
# Fancy indexing -> a COPY
picked = arr[[0, 2, 4]]
picked[0] = -1
print(picked) # [-1 30 50]
print(arr) # [10 20 30 40 50] -> untouched
# Boolean mask -> also a COPY
big = arr[arr > 25]
print(big) # [30 40 50]
print(big.base) # None -> confirms it's a copy
# reshape -> usually a VIEW
grid = arr.reshape(5, 1)
print(grid.base is arr) # True
# ✅ Expected output:
# [-1 30 50]
# [10 20 30 40 50]
# [30 40 50]
# None
# TrueExpected output: [-1 30 50] with arr unchanged, the mask result [30 40 50] with base of None, and True showing reshape gives a view.
🎯 YOUR TURN: Fill in the Blanks
Replace each ___ so editing the slice does NOT change the original array.
import numpy as np
original = np.array([1, 2, 3, 4, 5])
# Make an INDEPENDENT slice so the original is protected
piece = original[0:3].___() # fill in the method that makes a copy
piece[0] = 99
print(piece) # [99 2 3]
print(original) # Expected output: [1 2 3 4 5]
# Confirm it is a copy, not a view
print(piece.base) # Expected output: ___ (fill in what a copy's base is)Expected output: [99 2 3], the original still [1 2 3 4 5], and None. (Answers: copy, None.)
⚠️ Common Errors & Quick Reference
❌ My original array changed when I only edited a slice
The slice was a view sharing memory with the source.
✅ Fix: call .copy() on the slice to get an independent array before modifying it.
❌ Expected fancy indexing to update the original
Storing arr[[0, 2]] in a new variable gives a copy, so edits to it do nothing to arr.
✅ Fix: assign directly back, e.g. arr[[0, 2]] = 0, to change the original in place.
| Operation | Returns |
|---|---|
| Basic slice arr[1:4] | View (shared memory) |
| arr.copy() | Copy (independent) |
| Fancy index arr[[0, 2]] | Copy |
| Boolean mask arr[arr > 5] | Copy |
| arr.reshape(...) | Usually a view |
🏆 Mini Challenge: Safe vs Shared
Create one slice as a view and one as a copy, change both, and prove that only the view affected the original.
import numpy as np
base_arr = np.array([5, 10, 15, 20])
shared = base_arr[:2] # view
independent = base_arr[2:].copy() # copy
shared[0] = -100 # mutates base_arr
independent[0] = -100 # does NOT mutate base_arr
print(base_arr) # [-100 10 15 20]
print("shared.base is base_arr:", shared.base is base_arr) # True
print("independent.base:", independent.base) # None
# ✅ Expected output:
# [-100 10 15 20]
# shared.base is base_arr: True
# independent.base: None❓ Frequently Asked Questions
Lesson complete — no more memory surprises!
You now know that slices are views, .copy() makes independent arrays, .base reveals the link, and fancy and boolean indexing always copy. The classic gotcha can't catch you anymore.
Practice quiz
What is a view in NumPy?
- A read-only array
- A new array sharing the same memory as the original
- An independent copy of the data
- A 1D version of an array
Answer: A new array sharing the same memory as the original. A view shares memory, so changing one changes the other.
What does basic slicing like arr[1:4] return?
- A view that shares memory
- A copy
- A scalar
- An error
Answer: A view that shares memory. Basic slicing returns a memory-sharing view of the original.
If view = original[1:4] and you set view[0] = 999, what happens to original?
- It raises an error
- Nothing changes
- The corresponding element of original also changes
- original is deleted
Answer: The corresponding element of original also changes. Because the slice is a view, writing through it mutates the original.
How do you make an independent slice that does not affect the original?
- arr[1:4].view()
- arr[1:4].flatten()
- arr[1:4].reshape()
- arr[1:4].copy()
Answer: arr[1:4].copy(). .copy() allocates fresh memory so edits stay isolated.
For a view v of array a, what is v.base?
- None
- The original array a
- An empty list
- 0
Answer: The original array a. A view's .base points back to the array it shares memory with.
What is the .base of an array returned by .copy()?
- None
- The original array
- Itself
- True
Answer: None. A copy owns its data, so its .base is None.
Does fancy indexing like arr[[0, 2, 4]] return a copy or a view?
- A view
- It depends on the dtype
- A copy
- Neither; it errors
Answer: A copy. Fancy indexing always returns a new copy.
Does boolean indexing like arr[arr > 25] return a copy or a view?
- A view
- A scalar
- Nothing
- A copy
Answer: A copy. Boolean (mask) indexing always returns a copy.
What does arr.reshape(5, 1) usually return?
- A scalar
- A view of the same data
- A copy
- An error
Answer: A view of the same data. reshape usually returns a view because it reinterprets the same buffer.
How can you confirm two arrays share memory?
- np.shares_memory(a, b)
- a == b
- a.copy()
- len(a)
Answer: np.shares_memory(a, b). np.shares_memory tests whether the arrays overlap in memory.
Continue this course
- Previous: Stacking & Splitting Arrays
- Next: Random Number Generation — Generate reproducible random data with the modern Generator API
- Quick reference: NumPy cheat sheet
Frequently asked questions
What is the difference between a copy and a view in NumPy?
A view is a new array object that looks at the same underlying memory as the original, so changing one changes the other. A copy is an independent array with its own memory, so changing the copy leaves the original untouched. Basic slicing returns a view, while .copy() and fancy indexing return copies.
Does slicing return a copy or a view?
Basic slicing such as arr[1:4] returns a view that shares memory with the original array. This is fast and memory-efficient, but it means modifying the slice also modifies the original. Call .copy() on the slice if you need an independent array.
How can I tell if an array is a view or a copy?
Check the .base attribute. If arr.base is None, the array owns its data and is not a view of anything. If arr.base points to another array, it is a view that shares that array's memory. You can also use np.shares_memory(a, b) to test whether two arrays overlap.
Does fancy or boolean indexing return a view?
No. Fancy indexing with a list of integer indices and boolean (mask) indexing both return a copy, never a view. That means assigning to a separately stored fancy-indexed result does not change the original array. Assigning directly back into the array, like arr[mask] = 0, still works in place.