Skip to main content

Command Palette

Search for a command to run...

i didn't understand looping in python, so I dug a little deeper

Published
4 min read

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:

  1. Iterators

  2. Name binding

  3. 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 arr

    • a 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 arr is made

  • a new iterator object is created

  • each iteration binds x to 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 1

  • binding 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:

  • i is just an integer

  • arr[i] refers to a specific location inside the list object

  • assignment 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)

CodeWhat changes
x = 10name binding
x = x + 1name rebinding
arr[i] = 5list 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?

  • x is bound to the same inner list object

  • .append() mutates that object

  • the 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 seq never mutates seq by itself

  • only 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.