Why can a function modify some arguments as perceived by the caller, but not others?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
A Python function seems to modify some arguments but not others because the visible effect depends on mutation versus rebinding. Python passes object references into functions, and what the caller notices depends on whether the function changes the shared object itself or only points its local parameter name at a different object.
Names Point to Objects
A function parameter is a local name bound to the same object that the caller passed.
What matters is what the function does next. The function does not receive the caller's variable name. It receives a reference to the object.
Mutation Is Visible to the Caller
If the object is mutable and the function changes it in place, the caller sees that change because both sides still refer to the same object.
The function did not replace the caller's variable. It mutated the existing list object.
Rebinding Only Changes the Local Name
If the function assigns a new object to its parameter name, that rebinding is local to the function.
The integer object used by value was not changed. The local name n was rebound to a different integer object.
The same rule applies even for mutable objects if you rebind instead of mutate.
The original list remains unchanged.
Mutability Explains the Illusion
Lists, dictionaries, and sets are mutable. Integers, strings, and tuples are immutable. That difference creates the impression that Python uses different parameter-passing modes for different types.
It does not. The language model is consistent. The difference is simply whether the shared object can be changed in place.
Returning a New Object Versus Mutating in Place
This distinction is also an API design choice. Some functions are meant to mutate the object they receive. Others should leave the original untouched and return a new value.
Being explicit about that contract makes functions easier to use and test.
A Helpful Mental Model
Think in two layers:
- names are bindings
- objects hold the actual data
Mutation changes the object. Rebinding changes only which object a name refers to. Once that clicks, most of the confusion disappears.
The Same Rule Applies Everywhere
The same object model explains dictionaries, sets, and custom objects too. If a function mutates the shared object, the caller observes the new state. If the function replaces its local name with a different object, the caller does not. Once you stop thinking in terms of caller variables and start thinking in terms of object identity, the behavior becomes consistent.
Common Pitfalls
A common mistake is saying Python is purely pass-by-reference or purely pass-by-value without explaining object references and rebinding.
Another mistake is assuming parameter assignment updates the caller's variable. It does not.
Also be careful with mutable default arguments. They preserve state across calls for the same reason mutation is visible through shared references.
Summary
- Python passes object references into functions.
- If a function mutates a shared mutable object, the caller sees the effect.
- If a function rebinds its local parameter, the caller's variable does not change.
- The apparent inconsistency comes from mutability, not from multiple passing mechanisms.
- Understanding mutation versus rebinding explains most argument-behavior questions.

