Learn by reading through in order

Mutable and Immutable Types

Learn the difference between mutable and immutable types in Python and dodge the shared-reference bug right in your browser.

Why you need to learn this

This article isn't for complete beginners, but the topic is unavoidable if you want a deep understanding of programming. It's a common story for learners to hit a mutability bug and spend hours debugging it.

In Python, you might think y = x just connects two variables with an equals sign, only to find that editing one rewrites the other too. That's mutability.

This chapter nails down the difference between "mutable" (changeable) and "immutable" (unchangeable), and shows you how to copy safely.

Type classification — which side does each belong to?
Immutableint, floatstr, bool, tupleOne changed,other staysMutablelist, dictsetOne changed,both changeincludestraitincludestrait

A type that lets you rewrite the variable itself (via append, element assignment, update, and the like) is mutable; one that doesn't is immutable.

list / dict / set are mutable, and everything else (int / float / str / bool / tuple) is immutable.

Immutable types — changing one doesn't affect the other

With an immutable type (an unchangeable type), once you've passed a value with y = x, changing x later has no effect on y.

For example, after x += 1, x becomes 11, but y stays at the original 10.

How assignment flows for immutable types
x = 10y = xx += 1x = 11y = 10runresult
# int is immutable
x = 10
y = x
x += 1
print(x)   # 11
print(y)   # 10  <- unchanged

# str is immutable too
x = "hello"
y = x
x = x + " world"
print(x)   # hello world
print(y)   # hello

# tuple is immutable too
x = ("a", "b")
y = x
x = ("c", "d")
print(x)   # ('c', 'd')
print(y)   # ('a', 'b')

With immutable types, once you've saved a value into another variable and then update the original, the copy isn't affected.

① Copy a product price price = 1000 into another variable old_price, then raise the price with price = price + 500. Print both price and old_price.

② Copy a tag tuple tags = ("sale", "new") into old_tags, then rebind tags to a different tuple with tags = ("limited", "gift"). Print tags and old_tags.

(Get both parts right and the explanation will appear.)

Python Editor

Run code to see output

Python variables are "name tags on boxes"

A Python variable doesn't hold the value itself — it's more like a name tag pointing to data (a box) in memory. When you write y = x, Python doesn't make a new box; it sticks another name tag (y) on the same box that x points to.

- Immutable types: rebinding like x = 11 just moves x's name tag onto a different box. y still points to the original box, so its value stays independent.

- Mutable types: an in-place operation like x.append(...) mutates the shared box itself, so the change is visible through y too.

This is the line that splits immutable from mutable.

Mutable types — y = x makes changing one change the other

With a mutable type (list / dict / set), though, writing y = x leaves y and x pointing at the same box.

If you then do something that rewrites the contents, like x.append(...), the change shows up through y too.

How assignment flows for mutable types — sharing the same box
x=[a,b]y=xx.append(c)x=[a,b,c]y=[a,b,c]runboth

y = x doesn't create a separate copy — both names point at the same place.

Operations that rewrite the contents, like append, change the shared box both name tags refer to, so the change is visible through y as well.

# list is mutable
x = ["a", "b"]
y = x          # just adds another name tag y to the same list

x.append("c")  # rewrites the contents directly
print(x)       # ['a', 'b', 'c']
print(y)       # ['a', 'b', 'c']  <- y grew too!

# remove behaves the same way
x.remove("b")
print(y)       # ['a', 'c']  <- disappears from y too

# dict and set show the same behavior
d = {"k": 1}
e = d
e["new"] = 99
print(d)       # {'k': 1, 'new': 99}  <- d grew too

Why is sharing the default, anyway?

If y = x copied the whole contents every time, then when, say, x holds millions of records fetched from a database, memory and runtime would balloon fast.

So in Python, a variable name isn't the value itself — it's a name tag pointing at a location in memory.

You can't change this mechanism, so it's on you (the writer) to use it carefully.

Let's see the sharing that mutable types cause.

① Make a shopping cart cart = ["milk", "bread"], then write old_cart = cart (meaning it as a copy).

② Think you're adding "egg" to cart alone with cart.append("egg"), then print both cart and old_cart.

Both should contain "egg". That's today's trap.

Python Editor

Run code to see output

Use copy() to get an independent version

When you want to keep the original data and create a separate version, use the copy() method.

Writing y = x.copy() copies the contents into a new box and hands that to y, so changes to x afterwards don't affect y.

copy() creates a "separate box"
x=[a,b]y=x.copy()x.append(c)x=[a,b,c]y=[a,b]runindependent

y = x.copy() allocates a new region of memory at that moment.

After that, rewriting x's contents with x.append(...) or similar has no effect on y.

# list / dict / set all have .copy()
x = ["apple", "lemon"]
y = x.copy()

x.append("grape")
print(x)   # ['apple', 'lemon', 'grape']
print(y)   # ['apple', 'lemon']  <- not affected

# dict.copy() works the same
d = {"a": 1, "b": 2}
e = d.copy()
d["c"] = 3
print(d)   # {'a': 1, 'b': 2, 'c': 3}
print(e)   # {'a': 1, 'b': 2}  <- not affected

# set.copy() works the same
s = {1, 2}
t = s.copy()
s.add(3)
print(s)   # {1, 2, 3}
print(t)   # {1, 2}            <- not affected

# list has a few other ways to copy
x = ["apple", "lemon"]
y1 = list(x)   # passing through the constructor gives a new list
y2 = x[:]      # full slice copy (from start to end)

Watch out when your list contains other lists

copy() only builds a new outer box. Mutable values inside it (like lists inside a list) stay shared. This is called a shallow copy.

Be careful with nested data.

Let's fix the bug from the previous section with copy().

Make cart = ["milk", "bread"], but this time write old_cart = cart.copy(). Run cart.append("egg"), then print cart and old_cart. "egg" should not end up in old_cart this time.

Python Editor

Run code to see output

In this article you learned the difference between mutable and immutable types, and how copy() gives you an independent version.

The idea that a variable name isn't the value itself but a name tag pointing at a location in memory carries over to other languages too, not just Python. When you work with mutable types, slip in a copy().

QUIZ

Knowledge Check

Answer each question one by one.

Q1Which of these groups contains only mutable types?

Q2What is the value of b after running the following code?

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

Q3You want to keep the contents of the original list a and get an independent version b. Which is the most appropriate way to write it?