Understanding Mutable and Immutable Types
Lists are passed to functions by reference, meaning the function receives access to the original list, not a copy.
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.
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.
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.
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'].
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.
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.
| Function action | What happens to the list object | What the caller observes |
|---|---|---|
| Modify the list through the parameter | The shared list object changes | The caller sees the modification |
| Reassign the parameter | The parameter refers to a different list | The 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 item | Description from the source | Effect of a function's modification |
|---|---|---|
| Number | A value | The original number outside the function stays unchanged |
| List | An object accessed through a reference | Changes 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.
Apply the Trace
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.
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
- Passing a list to a function gives the function access to the original list rather than a copy.
- The caller's variable and the function parameter are aliases for the same list object.
- Mutating the list inside the function changes what the caller sees.
- Reassigning the parameter does not affect the caller's original list.
- 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.