Concepts / Understanding Mutable and Immutable Types

Understanding Mutable and Immutable Types

Lists are passed to functions by reference, meaning the function receives access to the original list, not a copy.

  • Programming

The List That Changes Elsewhere

A function call can change a list that appears to belong to the caller. This can feel surprising if you expect the function to receive a separate copy. When a list is passed to a function, the function receives access to the original list. If the function modifies that list, the caller sees the modification after the function returns.

What do you think happens?

Before tracing the call, predict what happens to letters after delete_head receives it and removes the element at index 0.

  • letters remains ['a', 'b', 'c']
  • letters becomes ['b', 'c']
  • letters becomes a separate copy with the same contents
Reveal answer

Answer: letters becomes ['b', 'c']

The function parameter and letters refer to the same list object. Removing the element through the parameter modifies that shared object.

Two Names, One List

The key idea is aliasing. The original variable in the caller and the function parameter are aliases for the same list object in memory. The parameter is not a new list containing copied elements. It is another name through which the function can access the existing list.

refers torefers toletterscaller variable['a', 'b', 'c']one list objecttfunction parameter
What does memory look like when the original variable and the function parameter refer to the same list object?

The diagram shows two names connected to one object. Because there is only one list object, an operation that removes or updates an item changes that object for both names. The caller does not need to receive a returned list to observe the change; its variable already refers to the modified object.

passesbinds to taccessescallerletterslettersreference to the listdelete_headparameter t['a', 'b', 'c']original list
How does the list reference move from the caller into the function when the function is called?

Tracing a Removal

The delete_head Example

A caller has letters containing ['a', 'b', 'c']. The function delete_head receives the list as parameter t and deletes the element at index 0. What does letters contain afterward?

Before the call: The caller's variable letters refers to a list containing ['a', 'b', 'c'].

During the call: The parameter t refers to the same list object. The deletion at index 0 removes 'a' from that object.

After the call: The function returns, but the list object remains modified. The caller's variable letters still refers to that same object.

letters contains ['b', 'c'].

refers tomodified by delete_headrefers toletters['a', 'b', 'c']list object['a', 'b', 'c']letters['b', 'c']list object['b', 'c']
What changes in the original list after the function removes the first item through its parameter?

The important event is not that the parameter t disappears when the function ends. The important event is that the list object was changed while t referred to it. Since letters also referred to that object, the caller observes the deletion.

1. passes letters2. removes index 03. caller sees ['b', 'c']callerlettersdelete_headparameter tlist['a', 'b', 'c']
What sequence of actions causes the caller's list to lose its first item?

Mutation Versus Reassignment

Changing the list object and changing what the parameter refers to are different operations. A modification such as deleting an element changes the shared list object, so the caller sees it. Reassigning the parameter to a different list does not change the caller's original list. After reassignment, the parameter refers to the new list instead, while the caller's variable continues to refer to the original list.

modifieschange is visiblerefers toseparate fromtshared parameteroriginal listcontents changecaller variablesees the changetnew bindingnew list[1, 2, 3]original listunchanged for caller
What is the difference between changing the shared list object and assigning the parameter to a new list?
Function actionWhat happens to the list objectWhat the caller observes
Modify the list through the parameterThe shared list object changesThe caller sees the modification
Reassign the parameterThe parameter refers to a different listThe caller's original list is not affected

The visible result depends on whether the function changes the shared object or changes only the parameter's reference.

Mutable and Immutable Values

The source contrasts lists with numbers. A number is described as a value; when it is passed to a function and modified inside the function, the original number outside the function stays unchanged. A list is an object, and passing it gives the function a reference to that object. Therefore, changes made to the list through the parameter are visible to the caller.

Passed itemDescription from the sourceEffect of a function's modification
NumberA valueThe original number outside the function stays unchanged
ListAn object accessed through a referenceChanges to the list are visible to the caller

Debugging Unexpected Changes

Unexpected list changes are often caused by a function call that received the list earlier. To investigate, trace every function call that passed the list as a parameter. Then check whether each function removed elements, updated items, or otherwise modified the list object. The function remove_negatives illustrates this pattern: it receives a reference to data and deletes elements through its parameter, so data is changed after the function returns.

  • Assuming that a function receives a separate copy of a list.

    The function parameter and letters are aliases for the same list object.

    Fix: Trace the list object through the call and check for modifications made through the parameter.

  • Treating reassignment and mutation as the same operation.

    Reassigning the parameter changes what the parameter refers to; it does not modify the caller's original list.

    Fix: Determine whether the function changed the shared list object or only reassigned its parameter.

  • Looking only at the final caller state.

    The mutation happened inside a function, while the visible result appeared later in the caller.

    Fix: Follow the sequence of calls and identify every function that received the list.

1. passes reference2. modifies list3. change remains visibledataoriginal listremove_negativesreceives datadel numbers[i]deletes elementsdatamodified list
What sequence of calls and mutations caused the original list to have unexpected final contents?

Apply the Trace

EASY

A list named records is passed to a function. Inside the function, the parameter is used to remove one item from the list. Predict whether records changes after the function returns, then explain which two names are aliases and which object was modified.

Hints
  • Ask whether the function changed the list object or merely reassigned its parameter.
  • The caller's variable and the parameter refer to the same list object when the function receives the list.
  • A removal changes the shared list object.
MEDIUM

A function receives data and reassigns its parameter to a new list containing [1, 2, 3]. Predict whether the caller's original data list changes. Explain why this differs from deleting an element through the parameter.

Hints
  • Reassignment changes what the parameter refers to.
  • The caller's variable continues to refer to the original list.
  • Deleting an element modifies the shared object; reassignment does not.

Key Takeaways

  1. Passing a list to a function gives the function access to the original list rather than a copy.
  2. The caller's variable and the function parameter are aliases for the same list object.
  3. Mutating the list inside the function changes what the caller sees.
  4. Reassigning the parameter does not affect the caller's original list.
  5. To debug an unexpected change, trace function calls that received the list and inspect which ones modified it.

Key Takeaways

  • A list passed to a function is accessed through a reference to the original list object.
  • The original variable and the function parameter are aliases for that same object.
  • List modifications made through the parameter are visible to the caller.
  • Parameter reassignment is different from list mutation and does not change the caller's original list.
  • Tracing function calls is the practical way to find the source of an unexpected list mutation.