One of the most common questions I get from developers transitioning to Python is whether the language uses pass by reference or pass by value.
I have seen many bugs crop up simply because a developer didn’t realize how Python handles their data behind the scenes.
If you are coming from C++ or Java, you might expect certain behaviors when you pass a variable into a function.
However, Python follows a unique model often called “pass by assignment” or “pass by object reference.”
In this tutorial, I will explain exactly how this works using my firsthand experience with real-world scenarios.
Does Python Use Pass by Reference or Pass by Value?
Technically, the answer is neither. In Python, every variable name is just a label (a reference) that points to an object in memory.
When you pass an argument to a function, Python passes a copy of that reference, not a copy of the actual object data.
Whether the original data changes depends entirely on whether the object you are passing is mutable (like a list) or immutable (like a string or integer).
Method 1: Pass Immutable Objects (Simulating Pass by Value)
When I work with immutable objects like integers, strings, or tuples, Python behaves very similarly to “pass by value.”
Since you cannot change an immutable object in place, any attempt to modify it inside a function creates a new object in memory.
I recently worked on a project for a US-based retail company where I needed to adjust the price of items during a sales event.
Here is an example of what happens when you try to modify a simple integer:
def apply_discount(price):
# This creates a new local integer object
price = price - 5
print(f"Inside the function (Discounted): ${price}")
# Original retail price in USD
original_price = 100
apply_discount(original_price)
print(f"Outside the function (Original): ${original_price}")I executed the above example code and added the screenshot below.

In this example, the original_price remains 100 even after the function call.
This is because integers are immutable in Python, so the function only modified its own local copy of the reference.
Method 2: Pass Mutable Objects (Simulating Pass by Reference)
Things change significantly when you deal with mutable objects like lists, dictionaries, or sets.
If you pass a list to a function and modify that list using an in-place method (like .append()), the changes will reflect outside the function.
During a recent data analysis task involving US ZIP codes, I needed to sanitize a list of locations by adding a default region code.
Observe how the original list is affected:
def add_region_code(locations):
# This modifies the original object in memory
locations.append("US-EAST-01")
print(f"Inside the function: {locations}")
# List of US cities
city_list = ["New York", "Boston", "Philadelphia"]
add_region_code(city_list)
print(f"Outside the function: {city_list}")I executed the above example code and added the screenshot below.

Because a list is mutable, both the city_list and the function’s locations variable pointed to the same object in memory.
When I added the region code, I was modifying the same “box” that the variable outside the function was looking at.
Method 3: Avoid Side Effects via Shadowing
Sometimes, you might pass a mutable object but you don’t want to change the original data.
I often encounter this when processing payroll data for different US states; I want to calculate taxes without altering the base salary records.
If you reassign the variable inside the function using =, you break the link to the original object.
def process_payroll(salaries):
# Reassigning salaries creates a NEW local list
salaries = [s * 0.8 for s in salaries]
print(f"Local salaries after tax: {salaries}")
# Annual salaries of US employees
employee_salaries = [85000, 92000, 110000]
process_payroll(employee_salaries)
print(f"Original salary records: {employee_salaries}")I executed the above example code and added the screenshot below.

By using a list comprehension to reassign salaries, I created a new object.
The employee_salaries variable outside the function remains untouched because the local reference was redirected to a new memory address.
How to Check the Object Identity Using id()
If you are ever unsure whether your function is working on the original object or a new one, you can use the built-in id() function.
In my debugging sessions, I use this to see the unique memory address of an object. If the IDs match, they are the same object.
def check_identity(data):
print(f"Inside function ID: {id(data)}")
us_states = {"NY": "New York", "CA": "California"}
print(f"Outside function ID: {id(us_states)}")
check_identity(us_states)If you run this code, you will notice the IDs are identical, proving that Python passed the reference to the same dictionary.
Understanding how Python handles object references is vital for writing clean, bug-free code.
Whether you are dealing with financial records or simple strings, knowing the difference between mutable and immutable behavior will save you hours of debugging.
You may also read:
- Understand for i in range Loop in Python
- How to Handle Python Command Line Arguments
- How to Add Python to PATH
- How to Read a File Line by Line in Python

I am Bijay Kumar, a Microsoft MVP in SharePoint. Apart from SharePoint, I started working on Python, Machine learning, and artificial intelligence for the last 5 years. During this time I got expertise in various Python libraries also like Tkinter, Pandas, NumPy, Turtle, Django, Matplotlib, Tensorflow, Scipy, Scikit-Learn, etc… for various clients in the United States, Canada, the United Kingdom, Australia, New Zealand, etc. Check out my profile.