Python Variables & Memory: The Deep-Dive Every Beginner Should Read


TL;DR
Variables in Python aren’t “boxes holding values.” They are names pointing to objects in memory.
CPython uses reference counting + a cycle-detecting garbage collector.
Mutable vs immutable objects behave differently with reassignment and function calls.
Use
==
for value equality,is
for identity checks.CPython performs interning and constant folding for optimization.
1) Variables Are Names, Not Boxes
Here’s a mindset shift most beginners miss: in Python, a variable is not a container for data. It's a label that points to an object somewhere in memory.
Think of it like a sticky note with the object’s address written on it: Name → Object (type, value, refcount)
2) Memory References
Assigning a variable binds a name to an object:
x = 10
y = x # y points to the same object as x
print(x, y) # 10 10
print(id(x), id(y)) # same memory identity
Rebinding changes the link, not the object:
x = [1, 2, 3]
y = x
y.append(4)
print(x) # [1, 2, 3, 4]
Why it matters: Mutating objects can surprise you if multiple names point to the same thing.
3) Reference Counting in CPython
CPython tracks how many references point to each object. When the count hits 0, the object is cleaned up:
import sys
a = [1, 2, 3]
print(sys.getrefcount(a)) # +1 from temporary reference
b = a
print(sys.getrefcount(a)) # count increased
del b
print(sys.getrefcount(a)) # back down
Note: Other Python implementations, like PyPy, don’t rely on reference counting as heavily.
4) Garbage Collection & Cycles
Reference counting can’t handle cycles (e.g., A → B → A). Python’s cycle collector solves this:
import gc
class Node:
def __init__(self):
self.ref = None
a, b = Node(), Node()
a.ref, b.ref = b, a
del a, b
print("Collected:", gc.collect())
Tip: Avoid __del__
on objects in cycles, it can delay collection.
5) Dynamic vs Static Typing
Python is dynamically typed (names can point to any object) and strongly typed (no silent coercion):
x = 10
x = "ten" # totally fine
# x + 5 # TypeError
Type hints are for humans & IDEs, not enforced at runtime.
6) Variable Reassignment
Reassignment changes which object a name points to:
x = 10
old_id = id(x)
x = 20
print(old_id == id(x)) # False
Mutable objects behave differently:
nums = [1, 2]
nums = nums + [3] # creates new list
nums.append(4) # mutates the same list
7) Mutable vs Immutable Objects
Immutable: int, float, bool, str, tuple, frozenset
Mutable: list, dict, set
s = "hi"
s += "!" # new string object
d = {"a":1}
d["b"] = 2 # same dict object
Tuples can contain mutable objects:
t = ([], 42)
t[0].append("boom")
print(t) # (['boom'], 42)
8) Functions, Arguments & Mutability
Python passes object references to functions (“call-by-sharing”):
def mutate(lst): lst.append(99)
def rebind(lst): lst = lst + [99]
a = [1,2,3]
mutate(a) # [1,2,3,99]
rebind(a) # still [1,2,3,99]
Avoid mutable default arguments:
def good(x, bucket=None):
if bucket is None:
bucket = []
bucket.append(x)
return bucket
9) Shared References Gotchas
Two names pointing to the same mutable object share changes:
a = [1,2]; b = a
b.append(3)
print(a) # [1,2,3]
Copying:
import copy
x = [[1],[2]]
y = copy.copy(x) # shallow copy
z = copy.deepcopy(x) # deep copy
10) Equality: ==
vs is
==
→ value equalityis
→ identity (same object)
x = [1,2,3]; y = [1,2,3]
print(x==y, x is y) # True, False
a = None; b = None
print(a is b) # True
Use
is None
for sentinel checks.
11) Everything is an Object
Names live in namespaces (dicts under the hood):
x = 42
def f(): pass
print(type(x), type(f))
print(globals().keys())
LEGB rule: Local → Enclosing → Global → Builtin
12) CPython Optimizations: Interning
Small integers and some strings are interned to save memory:
a = 256; b = 256
print(a is b) # True
a = 257; b = 257
print(a is b) # Might be False
Tip: Use ==
for value comparison.
13) Constant Folding & Peephole Optimizations
Python precomputes simple expressions at compile-time:
x = 2*10 # folded to 20
y = ("a"+"b") # folded to "ab"
import dis
def demo(): return 2*10
dis.dis(demo)
Big-O matters more than these micro-optimizations.
Quick Checklist
Names point to objects, not values.
==
for value,is
for identity.Mutable vs immutable.
Avoid mutable default args.
Copy carefully (
copy()
vsdeepcopy()
).Cycles need garbage collector.
Interning is just optimization.
Clarity > clever micro-optimizations.
Wrap-Up
The key takeaway: Python names point to objects. Once you internalize that, mutability, GC, equality, and optimizations suddenly make sense.
Subscribe to my newsletter
Read articles from Anik Sikder directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Anik Sikder
Anik Sikder
Full-Stack Developer & Tech Writer specializing in Python (Django, FastAPI, Flask) and JavaScript (React, Next.js, Node.js). I build fast, scalable web apps and share practical insights on backend architecture, frontend performance, APIs, and Web3 integration. Available for freelance and remote roles.