i didn't understand looping in python, so I dug a little deeper
If you’ve ever wondered why this code doesn’t change a list:
arr = [1, 2, 3]
for x in arr:
x = 10
but this one does:
for i in range(len(arr)):
arr[i] = 10
then you’ve bumped into three core Python concepts that are often explained badly:
Iterators
Name binding
Mutation vs rebinding
This post explains all three from first principles, with a precise mental model you can reuse everywhere.
1. Python variables are names, not boxes
In Python, a variable does not store a value.
It is just a name bound to an object.
x = 10
Means:
name "x" ──→ object 10
There is no box called x holding 10.
Rebinding
x = 20
Does not modify 10. It simply rebinds the name:
"x" now points to 20
This idea—assignment changes bindings, not objects—is the foundation for everything that follows.
2. What exactly is an iterator?
An iterator is an object that:
remembers where it is in a sequence
produces the next element on demand
In Python terms, it implements:
__iter__()__next__()
Creating an iterator
arr = [1, 2, 3]
it = iter(arr)
Yes — this creates a new object.
But importantly:
the list is not copied
the iterator only stores:
a reference to
arra current position (index)
Conceptually:
iterator
├─ reference → arr
└─ position → 0
Calling:
next(it)
returns the next element and advances the position.
3. What for x in arr really means
This loop:
for x in arr:
...
is roughly equivalent to:
it = iter(arr)
while True:
try:
x = next(it)
except StopIteration:
break
...
Key points:
no copy of
arris madea new iterator object is created
each iteration binds
xto the next element
4. Name binding inside a loop
Consider:
arr = [1, 2, 3]
for x in arr:
x = 10
Step-by-step (first iteration)
iterator yields
1binding happens:
x ──→ 1
- then:
x = 10
rebinding occurs:
x ──→ 10
The list is untouched:
arr ──→ [1, 2, 3]
This repeats for every element.
Nothing ever writes into the list.
5. Why index-based mutation works
Now compare:
for i in range(len(arr)):
arr[i] = 10
Here:
iis just an integerarr[i]refers to a specific location inside the list objectassignment writes into the list’s internal storage
Result:
arr ──→ [10, 10, 10]
This is called mutating the sequence by index.
6. Mutation vs rebinding (critical distinction)
| Code | What changes |
x = 10 | name binding |
x = x + 1 | name rebinding |
arr[i] = 5 | list mutation |
x.append(5) | object mutation |
Only mutation changes the object itself.
7. Why mutation sometimes works without indices
Consider:
arr = [[1], [2], [3]]
for x in arr:
x.append(99)
Result:
[[1, 99], [2, 99], [3, 99]]
Why does this work?
xis bound to the same inner list object.append()mutates that objectthe outer list still points to it
No rebinding — only mutation.
8. When you actually need indices
Use indices only when:
you must assign back to the same position
the new value depends on position
you’re modifying the container itself
Example:
for i in range(1, len(arr)):
arr[i] += arr[i - 1]
This cannot be expressed safely without indices.
9. The one mental model to remember
Iteration binds names. Assignment rebinds names. Mutation changes objects.
iterators do not copy data
for x in seqnever mutatesseqby itselfonly
seq[i] = ...or mutating methods modify containers
Once this clicks, Python’s behavior stops being surprising and starts being predictable.
If you understand this, you understand 90% of Python’s iteration and mutation semantics.