Mutable vs Immutable in Python (With Examples)

Mutable vs immutable in Python comes down to one question: can the object change after it’s created? Lists, dictionaries and sets are mutable, so they change in place. Strings, ints, floats and tuples are immutable, so every “change” builds a new object.

cart = ["laptop"]
cart.append("mouse")     # the same list, now longer

city = "Austin"
city = city + ", TX"     # a brand-new string

I ran each snippet on Python 3.12.5, and the screenshots are the real Command Prompt output, ids and all.

It sounds academic, but it explains a whole family of bugs: a copy that isn’t a copy, a function that edits your data, and a default argument that remembers old calls.

How to see whether a Python object is mutable

id() gives every object a number that stays fixed for its lifetime. Compare it before and after a change, and you’ll see which types change in place:

# a list is changed in place: same object afterwards
cart = ["laptop"]
before = id(cart)
cart.append("mouse")
print("list :", cart, "| same object:", id(cart) == before)

# a string can't change, so + builds a new one
city = "Austin"
before = id(city)
city = city + ", TX"
print("str  :", city, "| same object:", id(city) == before)

# so does an int, even with +=
count = 10
before = id(count)
count += 1
print("int  :", count, "| same object:", id(count) == before)

# and a tuple
point = (30.27, -97.74)
before = id(point)
point += (149,)
print("tuple:", point, "| same object:", id(point) == before)

Output:

list : ['laptop', 'mouse'] | same object: True
str  : Austin, TX | same object: False
int  : 11 | same object: False
tuple: (30.27, -97.74, 149) | same object: False
Command Prompt output showing a Python list keeps the same id after append while a string, int and tuple get a new id after changing
Only the list is still the same object. The other three were replaced.

Notice that count += 1 didn’t change the number 10. It created 11 and moved the name count to it, which is why the id changed.

That’s the whole idea in one sentence: with an immutable type, you can rebind the name, but you can’t edit the object.

Which Python types are mutable and which are immutable?

Here’s a quick test across the built-in types. Python refuses to hash a mutable object, which makes hash() a handy probe:

samples = {
    "int": 42, "float": 19.99, "bool": True, "str": "Denver",
    "tuple": (1, 2), "frozenset": frozenset({1, 2}), "bytes": b"ok", "None": None,
    "list": [1, 2], "dict": {"TX": 1}, "set": {1, 2}, "bytearray": bytearray(b"ok"),
}

for name, value in samples.items():
    try:
        hash(value)
        kind = "immutable"
    except TypeError:
        kind = "MUTABLE"
    print(f"{name:10} {kind}")

Output:

int        immutable
float      immutable
bool       immutable
str        immutable
tuple      immutable
frozenset  immutable
bytes      immutable
None       immutable
list       MUTABLE
dict       MUTABLE
set        MUTABLE
bytearray  MUTABLE
Command Prompt output listing Python built-in types as mutable or immutable using hash
Every built-in immutable type hashes. The four mutable ones raise TypeError.
ImmutableMutable
int, float, complex, boollist
strdict
tupleset
frozensetbytearray
bytes, NoneMost objects of your own classes

One caution: a tuple is immutable, but if it holds a list, that list can still change. The section on the tuple trap below shows exactly what happens.

Assigning a mutable list doesn’t copy it

This is where mutability usually bites first. backup = prices doesn’t make a copy; it gives the same list a second name:

prices = [19.99, 5.49]
backup = prices              # not a copy: a second name for the same list
prices.append(12.00)
print("backup:", backup)
print("same list?", backup is prices)

snapshot = prices.copy()     # a real copy
prices.append(3.25)
print("snapshot:", snapshot)

# with an int, changing one name never touches the other
a = 100
b = a
a += 1
print("a =", a, "| b =", b)

Output:

backup: [19.99, 5.49, 12.0]
same list? True
snapshot: [19.99, 5.49, 12.0]
a = 101 | b = 100

Because the list is mutable, a change through either name shows up in both. .copy(), list(prices) or prices[:] make a real copy.

Those all make shallow copies, which matters when the list holds other lists. Shallow copy vs deep copy covers that case.

With the int, nothing surprising happens, because you can’t change 100 in place. If you want to test whether two names point at one object, use is; is vs == explains the difference.

Mutable arguments: why a function can change your list

Python passes the object itself to a function, not a copy. So a function can edit a list you pass in, but it can’t change your int, float or string:

def add_tax(amounts):
    """Changes the caller's list in place (8.25% Texas sales tax)."""
    for i in range(len(amounts)):
        amounts[i] = round(amounts[i] * 1.0825, 2)

def add_fee(total):
    """Rebinds a local name; the caller's float is untouched."""
    total += 2.50
    return total

order = [100.00, 40.00]
add_tax(order)
print("list after add_tax:", order)

total = 50.00
add_fee(total)
print("float after add_fee:", total)
total = add_fee(total)
print("float after using the return value:", total)

Output:

list after add_tax: [108.25, 43.3]
float after add_fee: 50.0
float after using the return value: 52.5

add_tax edited the caller’s list directly. add_fee only moved its own local name, so the caller’s total stayed 50.0 until we used the return value.

My rule of thumb: if a function changes a list it was given, say so in its name or docstring. Otherwise, return a new list and leave the input alone.

The mutable default argument bug

This one catches nearly everyone once. A default value is created a single time, when the function is defined, so a mutable default is shared by every call:

def add_item(item, cart=[]):          # the default list is created ONCE
    cart.append(item)
    return cart

print(add_item("pen"))
print(add_item("ink"))                # the "empty" cart already holds the pen

def add_item_fixed(item, cart=None):  # use None, then make a new list
    if cart is None:
        cart = []
    cart.append(item)
    return cart

print(add_item_fixed("pen"))
print(add_item_fixed("ink"))

Output:

['pen']
['pen', 'ink']
['pen']
['ink']
Command Prompt output showing a Python function with a mutable default list keeping items between calls and the None fix
The second call’s “empty” cart already held the pen. The None version starts fresh each time.

Use None as the default and create the list inside the function. The same applies to dicts and sets, as default function arguments shows in more detail.

Can a tuple change if it contains a list?

Yes, the list inside it can. The tuple only fixes which objects it holds, not what happens inside them. And there’s a strange case with +=:

order = (["pen"], 4.99)      # an immutable tuple holding a mutable list

order[0].append("ink")       # changing the list inside is allowed
print(order)

try:
    order[0] += ["tape"]     # this raises an error...
except TypeError as err:
    print("TypeError:", err)

print(order)                 # ...and still changes the list

Output:

(['pen', 'ink'], 4.99)
TypeError: 'tuple' object does not support item assignment
(['pen', 'ink', 'tape'], 4.99)
Command Prompt output of a Python tuple containing a list where += raises TypeError but the list is still changed
The TypeError fired, and the list inside the tuple changed anyway.

order[0] += ["tape"] runs in two steps. First the list extends itself, which works, then Python tries to store the result back into the tuple, which fails.

So you get an error and a changed list. If that surprises you, you’re in good company; it’s one of the best-known oddities in the language.

Why dictionary keys and set items must be immutable

A dictionary finds a key by its hash. If the key could change after you stored it, its hash would change and the dictionary couldn’t find it again, so Python insists on hashable keys:

# tuples are immutable, so they work as dictionary keys
cities = {(30.27, -97.74): "Austin", (39.74, -104.99): "Denver"}
print(cities[(39.74, -104.99)])

# lists are mutable, so they don't
try:
    bad = {[30.27, -97.74]: "Austin"}
except TypeError as err:
    print("TypeError:", err)

# a set of sets needs frozenset
trips = {frozenset({"TX", "CO"}), frozenset({"NY"})}
print(frozenset({"CO", "TX"}) in trips)

Output:

Denver
TypeError: unhashable type: 'list'
True

Tuples make good keys for coordinates or pairs. When you need a set of sets, use frozenset, the immutable version of a set; set vs tuple compares the two.

Strings are immutable: you can’t change a character

Trying to edit one character in place raises an error. You build a new string instead, and string methods always return a new one:

city = "denver"

try:
    city[0] = "D"
except TypeError as err:
    print("TypeError:", err)

fixed = "D" + city[1:]        # build a new string instead
print(fixed, "| original:", city)

print(city.upper(), "| original:", city)   # methods return new strings too

Output:

TypeError: 'str' object does not support item assignment
Denver | original: denver
DENVER | original: denver

That’s why city.upper() didn’t change city. Assign the result if you want to keep it: city = city.upper().

Mutable vs immutable in Python: a quick comparison

Mutable (list, dict, set)Immutable (str, int, tuple)
Change in place?YesNo, you get a new object
Same id after a change?YesNo
Dictionary key or set item?NoYes, if everything inside is immutable
Safe as a default argument?No, use NoneYes
Can a function change the caller’s value?YesNo
Copy before editing?OftenNot needed

You might want to read these next:

Frequently asked questions

What is the difference between mutable and immutable in Python?

A mutable object can change after it’s created, like a list or dictionary. An immutable object can’t; operations that look like changes, such as s + "!", create a new object instead.

Is a string mutable in Python?

No. Strings are immutable, so s[0] = "x" raises a TypeError and methods like upper() return a new string.

Is a tuple mutable or immutable?

A tuple is immutable: you can’t add, remove or replace its items. If it holds a mutable object such as a list, that object can still change.

Why can’t a list be a dictionary key?

Dictionary keys must be hashable, and a list isn’t because it can change. Use a tuple instead.

Are integers immutable in Python?

Yes. x += 1 creates a new int and points x at it; the original number never changes.

How do I avoid the mutable default argument problem?

Use None as the default and create the list or dict inside the function: if cart is None: cart = [].

Where is mutability defined officially?

The Python data model describes objects, values and types, including which ones are mutable.