Mutability in Python Explained (Mutable vs Immutable Types)

Mutability describes whether a Python object can change after it is created.

This matters because Python variables do not store independent copied values by default. They refer to objects. Once you understand that, many confusing results with lists, strings, and function arguments start to make sense.

In this guide, you will learn:

  • What mutable and immutable mean
  • Which common Python types are mutable or immutable
  • How assignment works with objects
  • Why functions can sometimes change your original data
  • How to avoid common bugs

Quick example

x = [1, 2]
y = x
y.append(3)
print(x)  # [1, 2, 3]

name = "Sam"
new_name = name.upper()
print(name)      # Sam
print(new_name)  # SAM

Lists can change in place. Strings cannot. Many beginner mistakes come from not knowing this difference.

What mutability means

A mutable object can be changed after it is created.

An immutable object cannot be changed after it is created.

That sounds simple, but it affects how Python behaves in important ways:

  • If an object is mutable, code can change the same object in place
  • If an object is immutable, any “change” actually creates a new object
  • This matters most when more than one variable refers to the same object
  • It helps explain many surprising results in beginner Python code

Here is a simple example:

numbers = [1, 2, 3]
numbers.append(4)
print(numbers)

Output:

[1, 2, 3, 4]

The list was changed in place.

Now compare that to a string:

text = "cat"
text = text + "s"
print(text)

Output:

cats

This looks like the string changed, but it did not. Python created a new string and then reassigned text to point to it.

Common immutable types

Common immutable Python types include:

  • str
  • int
  • float
  • bool
  • tuple

With immutable objects:

  • You cannot change the object itself after creation
  • Operations usually create a new object
  • Reassigning a variable does not change the original object

Example with a string:

name = "Sam"
upper_name = name.upper()

print(name)
print(upper_name)

Output:

Sam
SAM

name.upper() does not change name. It returns a new string.

Example with an integer:

x = 10
x = x + 1
print(x)

Output:

11

Again, Python does not modify the original integer in place. It creates a new integer and assigns x to it.

If you want to learn more about string behavior, see Python strings explained.

Common mutable types

Common mutable Python types include:

  • list
  • dict
  • set

With mutable objects:

  • The object can be changed after creation
  • Many methods modify the object in place
  • If two variables refer to the same object, both will reflect the change

Example with a list:

items = ["a", "b"]
items.append("c")
print(items)

Output:

['a', 'b', 'c']

Example with a dictionary:

user = {"name": "Ana"}
user["age"] = 25
print(user)

Output:

{'name': 'Ana', 'age': 25}

Example with a set:

colors = {"red", "blue"}
colors.add("green")
print(colors)

Output:

{'red', 'blue', 'green'}

These methods change the original object instead of returning a brand-new one.

For more on list behavior, see Python lists explained and Python dictionaries explained.

Variables store references, not boxes of copied values

A beginner-friendly way to think about this is:

  • A variable name points to an object
  • Using = does not always make a new independent copy
  • Sometimes two variable names point to the same object

This is especially important with mutable types.

a = [1, 2]
b = a

b.append(3)

print(a)
print(b)

Output:

[1, 2, 3]
[1, 2, 3]

Why did a change when only b.append(3) was called?

Because a and b refer to the same list.

Now compare that with an immutable value:

x = "hi"
y = x

y = y.upper()

print(x)
print(y)

Output:

hi
HI

Here, x does not change because strings are immutable. y.upper() returns a new string, and y is updated to refer to that new object.

If you want a simple way to inspect object identity, Python has the id() function. It can help while debugging, but it is better to understand the behavior first instead of depending on id() alone.

Why mutability matters with functions

When you pass an object to a function, the function receives access to that same object.

That means:

  • If the object is mutable, the function can change the original
  • If the object is immutable, the function cannot change the original in place

Example with a list:

def add_item(my_list):
    my_list.append("new")

data = ["old"]
add_item(data)

print(data)

Output:

['old', 'new']

The function changed the original list.

Now compare that with a string:

def make_upper(text):
    text = text.upper()
    print("Inside function:", text)

name = "Sam"
make_upper(name)
print("Outside function:", name)

Output:

Inside function: SAM
Outside function: Sam

The original string did not change.

This is one reason beginners get confused by Python functions explained. It can seem like arguments are copied automatically, but that is not what happens.

Mutability and copying

Assignment shares a reference. It does not usually make a copy.

original = [1, 2, 3]
alias = original

alias.append(4)

print(original)

Output:

[1, 2, 3, 4]

If you need an independent list, make a copy:

original = [1, 2, 3]
copied = original.copy()

copied.append(4)

print("original:", original)
print("copied:", copied)

Output:

original: [1, 2, 3]
copied: [1, 2, 3, 4]

A shallow copy creates a new outer object, but nested objects may still be shared.

a = [[1, 2], [3, 4]]
b = a.copy()

b[0].append(99)

print(a)
print(b)

Output:

[[1, 2, 99], [3, 4]]
[[1, 2, 99], [3, 4]]

a and b are different outer lists, but the inner lists are still shared.

For more on this, see Python shallow copy vs deep copy explained, how to copy a list in Python, and the Python list copy() method.

How to check and reason about object behavior

When you are unsure whether something changes in place, use this process:

  • Look at the data type first
  • Check whether the method changes the object or returns a new one
  • Use id() only as a helper
  • Read the method documentation when needed

Useful debugging commands:

print(type(value))
print(id(value))
print(value)
print(value is other_value)
help(list.append)
help(str.replace)

A practical example:

text = "hello"
new_text = text.replace("h", "H")

print(text)
print(new_text)
print(text is new_text)

Output:

hello
Hello
False

str.replace() returns a new string.

Now compare that to a list method:

numbers = [1, 2]
result = numbers.append(3)

print(numbers)
print(result)

Output:

[1, 2, 3]
None

list.append() changes the list in place and returns None.

That difference is very important when reading Python code.

Beginner rules to remember

These rules are not everything, but they will help you avoid many bugs:

  • Strings and numbers usually create new objects
  • Lists, dictionaries, and sets often change in place
  • Be careful when reusing the same list or dictionary in multiple places
  • Copy mutable data when you need an independent version
  • Do not assume a = b makes a copy

Common mistakes

These are some very common beginner problems related to mutability:

  • Assigning one list to another variable and expecting a separate copy
  • Changing a list inside a function and being surprised the original changed
  • Expecting string methods to modify the original string
  • Using mutable default values in functions without understanding shared state
  • Assuming all Python objects behave like lists

Example of a shared-list mistake:

a = [1, 2]
b = a
b.append(3)

print(a)  # Unexpected for many beginners

Output:

[1, 2, 3]

Example of expecting a string to change in place:

name = "sam"
name.upper()
print(name)

Output:

sam

To keep the result, assign it:

name = "sam"
name = name.upper()
print(name)

Output:

SAM

FAQ

Are strings mutable in Python?

No. Strings are immutable. String operations return a new string.

Are lists mutable in Python?

Yes. Lists can be changed in place with methods like append(), insert(), remove(), and sort().

Does a = b copy a list?

No. It usually creates another reference to the same list.

Why did my function change my original list?

Because lists are mutable, and the function may have modified the same object you passed in.

How do I make an independent copy of a list?

Use list.copy(), slicing like my_list[:], or the copy module for more complex cases.

See also