Home Session 01
Foundations · Session 01

Variables & Mutability

What really happens when you write x = 42? Why can you change a list but not a string? We'll trace every step — through Python's memory, through references, through time.

~25 minutes
4 interactive demos
Beginner friendly
What you'll understand
Names vs objects id() & identity Mutation Rebinding Reference counting Aliasing
Session flow
  1. Step through each interactive demo below
  2. Read the explanation strip under the stage
  3. Scroll down for the full written narrative
1 Pick a demo below
2 Press ▶ Play or tap Next → to step through
3 Watch the Memory panel update in real time
4 Read the explanation below, then scroll down for concepts
Immutable
Mutable
python
Speed:
1 / 1
Memory
namespace heap
1

Initial state — empty namespace

Before any code runs, the global namespace is empty — no names, no objects. Press ▶ Play to autoplay, or use Next → to step through manually. Watch the Memory panel on the right as objects appear on the heap.

🔑
The most important thing to understand first
Python objects come in two flavours: immutable (cannot be changed after creation) and mutable (can be changed in-place). Demos 1 & 2 above demonstrate immutable types (int, str, float) — every "change" is actually a rebinding to a new object. Demos 3 & 4 demonstrate mutable types (list) — where you can change the object's contents without creating a new one. This distinction is the root cause of most Python surprises.

What is a variable in Python?

In many languages, a variable is a named box in memory. You declare int x = 5 and a piece of RAM labeled "x" holds the number 5. Change x, and the bytes inside that box change.

Python is different. Python variables are names — labels that point to objects. When you write x = 42, Python:

  1. Creates an integer object 42 somewhere in memory (the heap)
  2. Binds the name x in the current namespace to that object

The variable x doesn't "contain" 42. It references the object that contains 42. This distinction matters more than you'd think.

💡

Python's model: names and objects

In Python, everything is an object. Numbers, strings, functions, classes — all objects. Variables are just names bound to objects. Assignment (=) always binds a name to an object; it never copies the object itself.

What is id()?

Every object in Python has a unique identity: its memory address (or a proxy for it). The built-in function id(x) returns this address as an integer. Two variables with the same id() point to the exact same object — no copy was made.

python
x = 42 y = x print(id(x) == id(y)) # True — same object! print(x is y) # True — 'is' checks identity
🔎
is vs ==
== checks value equality — do the objects look the same?
is checks identity — are they literally the same object in memory?
Use is None, is True, is False for singletons. For everything else, prefer ==.
Reference Counting

How Python tracks when to free memory

Every heap object carries a hidden counter: its reference count. It records how many names (or containers) currently point to the object. CPython adjusts this counter automatically as variables are bound and unbound.

Refcount increases when a new name is bound to the object (x = obj, b = a, or passing it as a function argument).

Refcount decreases when a name is rebound (x = other), deleted (del x), or goes out of scope (function returns).

When refcount reaches 0, CPython immediately frees the object's memory. No waiting, no delay — garbage collected on the spot.

python
import sys x = [1, 2, 3] print(sys.getrefcount(x)) # 2 (x + getrefcount's argument) y = x # y also points to the list print(sys.getrefcount(x)) # 3 del y # y removed; refcount drops print(sys.getrefcount(x)) # 2 again
⚠️
Reference cycles
If two objects reference each other (e.g., a list containing itself), their refcounts never reach 0 even when nothing else points to them. CPython has a cyclic garbage collector that handles these cases, but it runs periodically — not immediately. This is why CPython doesn't only use reference counting.
Mutation vs Rebinding

The most important distinction in Python

Understanding the difference between mutation and rebinding is the single most useful mental model for reasoning about Python code.

The two-sentence summary

Rebinding (x = new_value) makes the name point to a different object. The old object is unaffected — it just loses one reference.
Mutation (x.append(), x[0] = ...) changes the contents of the existing object. All names referencing it see the change.

python
# ── REBINDING (immutable types) ───────────────────────────── s = "hello" t = s # both point to the same "hello" object s = "world" # s rebound to a NEW object; t still "hello" print(t) # hello — unaffected # ── MUTATION (mutable types) ───────────────────────────────── a = [1, 2, 3] b = a # both point to the same list a.append(4) # mutates the shared object print(b) # [1, 2, 3, 4] — b sees the change!

Immutable types (int, str, float, tuple) can only be rebounded — you can never change the object itself. Mutable types (list, dict, set) can be both mutated and rebound.

Operation Example Creates new object? id() changes? Other aliases see change?
Rebind x = 99 ✅ Yes ✅ Yes ❌ No
Augmented rebind x += 1 (int) ✅ Yes ✅ Yes ❌ No
Mutate (append) lst.append(x) ❌ No ❌ No ✅ Yes
Mutate (index) lst[0] = x ❌ No ❌ No ✅ Yes
Augmented rebind lst += [x] (list) ❌ No* ❌ No* ✅ Yes*

* For lists, += calls __iadd__ which mutates in-place (unlike with immutable types).

Python's Built-in Types

All the Variable Types

Python has many built-in types. The most important thing to know about each is: is it mutable or immutable?

int immutable
x = 42
x = -7
x = 0
Whole numbers, any size. Immutable: x += 1 creates a new int object, doesn't change the old one.
float immutable
x = 3.14
x = 2.0
x = -0.5
Decimal numbers (64-bit IEEE 754). Immutable. Watch out for floating point precision.
str immutable
s = "hello"
s = 'world'
s = """multi
line"""
Sequences of Unicode characters. Cannot be changed after creation — s[0] = 'H' raises a TypeError.
bool immutable
done = True
active = False
A subclass of int! True == 1, False == 0. Immutable singleton objects.
NoneType immutable
result = None
The absence of a value. A singleton — there is exactly one None object in the entire Python process.
tuple immutable
t = (1, 2, 3)
t = ("a", True)
Ordered, fixed-length sequence. You can't add/remove/change elements after creation. Great for function return values.
list mutable
nums = [1, 2, 3]
nums.append(4)
nums[0] = 99
Ordered, changeable sequence. You can add, remove, and update items in place. The object's identity stays the same.
dict mutable
d = {"a": 1}
d["b"] = 2
del d["a"]
Key-value store. Mutable and ordered (Python 3.7+). Keys must be immutable (hashable).
set mutable
s = {1, 2, 3}
s.add(4)
s.discard(1)
Unordered collection of unique items. Mutable — you can add/remove. But elements must be immutable (hashable).

Quick Reference: Mutability at a glance

Type Literal Mutable? Why it matters
int 42 ❌ No x += 1 rebinds x to a new object
float 3.14 ❌ No Any arithmetic produces a new float
str "hello" ❌ No String methods return new strings; you can't change characters in-place
bool True ❌ No Only two boolean objects exist; they're singletons
NoneType None ❌ No One None object exists; always use is None to check
tuple (1, 2) ❌ No Can't add/remove/change; safe to use as dict keys
list [1, 2] ✅ Yes Functions can modify the list you passed — be careful with aliases!
dict {"k": v} ✅ Yes Passed by reference; mutations inside functions persist
set {1, 2} ✅ Yes Can grow/shrink; elements must be immutable/hashable
⚠️
The aliasing trap
a = [1, 2, 3] then b = a does NOT copy the list. Both a and b point to the same list object. Doing b.append(4) will also affect a! Use b = a.copy() or b = list(a) to get an independent copy.
🔍
Integer interning
CPython (the standard Python) pre-creates integer objects for small values (typically -5 to 256). So id(5) == id(5) is always True, and two variables both set to 5 will share the exact same object. For large integers, each assignment creates a new object. This is an optimization detail, not a language guarantee.
The golden rule
Immutable objects are safe to share — no one can change them through another reference. Mutable objects need careful handling when passed around — anyone with a reference can change the object.