Concepts / List Mutability and In-Place Modifications

List Mutability and In-Place Modifications

Index-based iteration combines range() and len() to generate indices, enabling you to read and modify list elements.

  • Programming

A List That Changes While It Runs

Suppose a list contains numbers and your goal is to replace every number with its doubled value. The loop must reach each element, read its current value, calculate a replacement, and store that replacement in the same list position. This is an in-place modification: the list is updated as the loop runs.

What do you think happens?

If a loop doubles every element in the list [3, 5, 7], what list should remain after all iterations?

  • [3, 5, 7]
  • [6, 10, 14]
  • [9, 25, 49]
Reveal answer

Answer: [6, 10, 14]

Each element is read and replaced by its doubled value. The loop changes the list one position at a time.

From Length to Valid Indices

Index-based iteration combines range() and len(). len(my_list) supplies the list's length, and range(len(my_list)) supplies the sequence of indices used to reach the list elements. The loop variable therefore represents a position, not the element's value itself.

refers torefers torefers to0index3element1index5element2index7element
Which list element does each index from range(len(my_list)) refer to?
python
Output
0 3
1 5
2 7

Reading and Replacing One Position

The statement numbers[i] = numbers[i] * 2 performs two related actions. The expression on the right reads the old value at index i and multiplies it by 2. The assignment on the left writes the resulting new value back to that same position.

containsmultiply by 2containsindex iindex i5current value10replacement value
How does one statement use the current value at an index to calculate and store a replacement in that same position?
python
Output
[6, 10, 14]

Tracing Every Iteration

Doubling Each Element

Trace the loop for numbers = [3, 5, 7] and determine the final list.

Start: The list begins as [3, 5, 7].

First index: At the first index, the current value is 3. The loop stores 3 multiplied by 2 in that position, so the list becomes [6, 5, 7].

Second index: At the next index, the current value is 5. The loop stores 5 multiplied by 2 in that position, so the list becomes [6, 10, 7].

Third index: At the final index, the current value is 7. The loop stores 7 multiplied by 2 in that position, so the list becomes [6, 10, 14].

The final state of the list is [6, 10, 14].

double index 0double index 1double index 2[3, 5, 7]start[6, 5, 7]after index 0[6, 10, 7]after index 1[6, 10, 14]after index 2
What happens to the list after each loop iteration, and what is its final state?

Tracing means recording the list after each assignment, not only calculating the final answer. At every step, one position receives a new value while the other positions retain their current values.

Choosing the Loop Style

Iteration styleWhat the loop providesUse it when
Value-based iterationEach list value directlyYou only need to read the elements
Index-based iterationAn index used to reach each elementYou need to update elements in the list
iterate directlyiterate by positionList valuesread onlyList indicespositionsValue-based loopuse when readingIndex-based loopuse when updating
What is the difference between reading list values directly and iterating over indices for modification?

If the task is only to inspect or read each value, value-based iteration is sufficient. If the task requires replacing elements, index-based iteration provides the position needed for an assignment such as numbers[i] = numbers[i] * 2.

Mistakes in In-Place Updates

  • Using a loop that only supplies values when the task requires replacing list elements.

    Value-based iteration is suitable for reading, whereas updating requires index-based access.

    Fix: Use range(len(numbers)) and assign to numbers[i].

  • Reading the old value but not assigning the calculated value back to the list.

    A calculation alone does not write the replacement into the list position.

    Fix: Put the original value on the right side and the indexed list position on the left side of the assignment.

  • Predicting the final list without tracing the intermediate states.

    The loop changes one targeted position at a time, so each iteration can be checked separately.

    Fix: Record the list before the loop and after each index is processed.

Practice the Trace

EASY

Trace this loop without running it. Write the list after each iteration and then give the final state: values = [4, 1, 6]; for i in range(len(values)): values[i] = values[i] * 2

Hints
  • Use range(len(values)) to identify each index in order.
  • At each index, read the current value and replace it with that value multiplied by 2.
  • Write the whole list after the update at each position.

A reliable trace has three parts: identify the current index, read the value at that index, and write the replacement back to the same index. Repeating those three actions produces the final list state.

What to Remember

  1. range(len(my_list)) combines the list length with a generated sequence of indices for index-based iteration.
  2. numbers[i] = numbers[i] * 2 reads the old value at index i and writes a new value back to that same position.
  3. Use value-based iteration when you only need to read list elements.
  4. Use index-based iteration when you need to update list elements.
  5. Trace the list after each iteration to predict its final state.

Key Takeaways

  • range() and len() work together to provide indices for traversing a list by position.
  • An indexed assignment can read an element's old value and replace it in the same position.
  • Index-based iteration is appropriate for updates; value-based iteration is appropriate for reading.
  • Tracing each intermediate list state makes the final result predictable.