Avoid Common Python List Mistakes: [:] vs = Explained
Data Professional with experience in civic data, AI applications, and building data pipelines to solve real-world problems. My work with government datasets has given me unique domain knowledge of how AI can drive operational efficiency and informed decision-making.
I’m now focused on bringing this expertise to AI-driven companies, with a passion for building products with a purpose turning complex data into solutions that matter.
📧 madhura.anand@outlook.com
Disclaimer: All opinions and views expressed in any posts/blogs are my own and do not reflect the views or values of my organization.
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
numsis 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
numsto 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
iterablein 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 asliceobject: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 = numsstill 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