Avoid Common Python List Mistakes: [:] vs = Explained

Table of contents
- 🧩 Problem Statement: Rotating an Array Left by One
- ❌ Common Mistake: Using Reassignment
- ✅ The Correct Way: Slice Assignment
- 🔁 The Two Approaches
- 🔍 Under the Hood: What's Really Happening
- 🔬 Elaborating on Slice Assignment Internals
- 🔍 Step-by-Step: What Python Does Internally
- 🧠 Summary: Why [:] Triggers In-Place Change
- 🔬 Contrast: nums = new_list
- 🧠 Memory Behind the Scenes
- ✅ Summary
In Python, the difference between nums = ...
and nums[:] = ...
can be subtle but very powerful when working with lists. This article breaks down how each works under the hood, why [:]
makes in-place changes, and when to use each.
🧩 Problem Statement: Rotating an Array Left by One
Given an integer array nums
, rotate the array to the left by one.
Note: There is no need to return anything — just modify the given array in-place.
Let’s say:
nums = [1, 2, 3, 4, 5]
After rotation, it should become:
[2, 3, 4, 5, 1]
❌ Common Mistake: Using Reassignment
Many beginners might write:
nums = nums[1:] + [nums[0]]
At first glance, this looks correct — it creates the right result.
But what does it really do? Try guessing:
nums = [1, 2, 3, 4, 5]
ref = nums
nums = nums[1:] + [nums[0]]
print(ref) # ❓ What will this print?
Output:
[1, 2, 3, 4, 5] # ❌ NOT modified!
Why? Because this code creates a new list and rebinds nums
to it. The original list (and anything else pointing to it) remains unchanged.
✅ The Correct Way: Slice Assignment
To actually modify the original array, you should do:
nums[:] = nums[1:] + [nums[0]]
Now:
nums = [1, 2, 3, 4, 5]
ref = nums
nums[:] = nums[1:] + [nums[0]]
print(ref) # ✅ [2, 3, 4, 5, 1]
Here, the contents of the list are replaced in-place, so any reference to nums
sees the update.
🔁 The Two Approaches
1. nums = nums[1:] + [nums[0]]
Creates a new list by slicing and concatenating.
The variable
nums
is rebound to this new list.The original list becomes unreferenced and is eligible for garbage collection.
2. nums[:] = nums[1:] + [nums[0]]
Uses slice assignment to replace the entire contents of the list
nums
.The list object itself remains the same; only its contents are replaced.
No garbage is created; memory is reused.
🔍 Under the Hood: What's Really Happening
✅ nums[:] = new_list
Translates to
nums.__setitem__(slice(None), new_list)
.This is slice assignment.
It modifies the existing object in-place, keeping the same
id(nums)
.
❌ nums = new_list
Just rebinds the name
nums
to a new object.The old object is no longer referenced and becomes a candidate for garbage collection.
🔬 Elaborating on Slice Assignment Internals
Absolutely — let’s elaborate deeply on what happens when Python sees a slice on the left-hand side of an assignment like:
nums[:] = new_list
🔍 Step-by-Step: What Python Does Internally
💡 When you write nums[:] = new_list
:
Python interprets this as:
nums.__setitem__(slice(None, None, None), new_list)
Let’s break this down.
🧩 1. nums[:]
** becomes a slice
object**
nums[:] ➝ slice(None, None, None)
- This is Python’s way of saying:
➤ "Select the full list — from beginning to end with step size 1."
It is syntactic sugar for:
slice_obj = slice(None, None, None)
🔧 2. Python calls __setitem__
on the list object
nums.__setitem__(slice_obj, new_list)
This triggers a special method inside Python’s list object that replaces the list’s contents for the specified slice.
It’s like saying:
“Hey list object — replace the part matching this slice with
new_list
.”
⚙️ Internals of __setitem__
for slice:
The method __setitem__(s: slice, iterable)
will:
Compute the range of indexes the slice refers to
Remove those elements
Insert new elements from
iterable
in their placeAll while modifying the same underlying list object
✅ No new object is created
✅ Same memory, same identity (id(nums)
remains unchanged)
🔁 Example with __setitem__
manually:
nums = [1, 2, 3, 4, 5]
slice_obj = slice(None)
nums.__setitem__(slice_obj, [9, 9, 9])
print(nums) # [9, 9, 9]
✅ This is exactly what nums[:] = [9,9,9]
does.
🧠 Summary: Why [:]
Triggers In-Place Change
[:]
→ Creates aslice
object:slice(None, None, None)
nums[:] = ...
→ Triggers__setitem__
method on the list object__setitem__
replaces elements inside the list’s memorySo the list’s identity (memory address) doesn’t change
No rebinding of
nums
, so aliases likeother_ref = nums
still see the changes
🔁 Example with __setitem__
manually:
nums = [1, 2, 3, 4, 5]
slice_obj = slice(None)
nums.__setitem__(slice_obj, [9, 9, 9])
print(nums) # [9, 9, 9]
✅ This is exactly what nums[:] = [9,9,9]
does.
🔬 Contrast: nums = new_list
When you write:
nums = new_list
Python does not call any list method. Instead, it performs variable rebinding in the local scope:
# In stack frame
nums ──▶ new_list # Now points to a different object
The original list is no longer referenced by nums
.
There’s no slicing, no method call, no mutation — just a pointer update.
🧠 Memory Behind the Scenes
nums[:] = ...
→ invokes__setitem__
→ updates content of the same heap objectnums = ...
→ stack variable now points to a different heap object
Result:
nums[:] = ... ➝ modifies existing object in-place
nums = ... ➝ switches pointer to a new object
✅ Summary
Action | Object Mutated? | Reference Changed? | In-Place? |
nums[:] = new_list | ✅ Yes | ❌ No | ✅ Yes |
nums = new_list | ❌ No | ✅ Yes | ❌ No |
Use [:]
when:
You want to preserve references to the same list
You want to modify a list in-place inside a function or across multiple variables
Use =
when:
You want to assign an entirely new object to the variable
You don’t care about preserving other references
Subscribe to my newsletter
Read articles from Madhura Anand directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
