Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes
Sponsored Reserved space — layout preview until AdSense is connected

Python

You Think You Know Python Variables? Here's How Memory Really Works

Python variables aren't boxes—they're references. Learn how memory actually works with id(), aliasing, mutability, and garbage collection to avoid silent bugs.

June 2026 · 8 min read · 1 views · 0 hearts

You Think You Know Python Variables? Here's How Memory Really Works

Most Python tutorials tell you variables are "labels for data." That's technically true — but it hides the absolute weirdest, most powerful part of how Python actually stores things in memory.

Let’s pop the hood.

Variables Are Not Boxes

If you learned programming with C or Java, you probably think of a variable as a bucket you put a value into. Like:

x = 5  # put 5 in the box labeled x

But in Python, variables don't hold values. They reference objects.

Think of it like a name tag pinned on a real object. The object (say, the integer 5) lives somewhere in memory. The variable x is just a sticky note pointing to it.

This distinction changes everything.

The id() Trick

Every Python object has a unique identity (like a memory address). You can check it with id():

x = 5
y = 5
print(id(x))  # 140711111111040
print(id(y))  # 140711111111040 — same!

Wait — x and y point to the same object? Yes. Python caches small integers (-5 to 256) and reuses them for performance. This is called interning.

But try this:

a = 257
b = 257
print(id(a), id(b))  # Different addresses!

Now Python creates two separate objects. For large integers, it doesn't bother caching.

The "Aliasing" Trap

This reference model hits you hardest with mutable objects — lists, dicts, sets.

original_list = [1, 2, 3]
alias = original_list  # No copy! Just another name tag
alias.append(4)
print(original_list)  # [1, 2, 3, 4] — surprise!

alias and original_list point to the same list object. Changing one changes the "other" — because there is no other.

How to actually copy:

import copy

shallow_copy = original_list[:]          # works for simple lists
deep_copy = copy.deepcopy(original_list) # for nested structures

Mutability vs Immutability

Immutable objects (int, float, str, tuple, frozenset) can't be changed in place. When you "modify" a string, Python actually creates a new one:

s = "hello"
t = s.upper()   # Creates NEW string object
print(s)        # "hello" — unchanged

Mutable objects (list, dict, set, bytearray) can be changed. That's why two variables pointing to the same list is dangerous — they share the mutable object.

The Reference Counting Game

Python tracks how many references point to each object. When the count hits zero, the object gets garbage-collected.

x = [1, 2, 3]   # reference count = 1
y = x            # reference count = 2
del y            # reference count = 1
del x            # reference count = 0 → garbage collected

You can check yourself with sys.getrefcount() (but note: passing it to the function temporarily bumps the count by 1):

import sys
z = [1, 2, 3]
print(sys.getrefcount(z))  # 2 (the variable + the function argument)

Circular References and the Garbage Collector

What happens when two objects reference each other?

a = []
b = [a]
a.append(b)   # Now a contains b, b contains a
del a
del b         # Reference counts never reach zero!

Python's cyclic garbage collector handles this. It periodically scans for groups of objects that reference each other but have no external references — and cleans them up. You rarely need to think about it, but gc.collect() can force a sweep in tight memory loops.

The is vs == Headache

  • == checks value equality ("do these objects have the same contents?")
  • is checks identity equality ("do these variables point to the same object?")
x = [1, 2, 3]
y = [1, 2, 3]
print(x == y)  # True — same values
print(x is y)  # False — different objects in memory

Always use is for None comparisons, and == for everything else.

Memory for Function Arguments

When you pass an argument to a function, Python passes the reference, not a copy. This is often called "pass by object reference."

def modify(lst):
    lst.append(4)

my_list = [1, 2, 3]
modify(my_list)
print(my_list)  # [1, 2, 3, 4] — modified in place

But reassigning inside a function doesn't affect the outer variable:

def reassign(lst):
    lst = [10, 20, 30]  # Points local name to new object

my_list = [1, 2, 3]
reassign(my_list)
print(my_list)  # [1, 2, 3] — unchanged

The local variable lst now points to a new list, but the outer my_list still points to the original.

The Takeaway

Understanding Python's memory model isn't academic trivia — it's the difference between bugs you chase for hours and clean, predictable code.

Concept Problem It Solves
Variables are references Avoid accidental mutation with aliases
id() and is Debug variable identity
Immutability Safe sharing across functions
Reference counting + GC Memory management understanding

Next time you write a = b, ask yourself: Am I making an alias, or do I need a copy? That question alone will save you more headaches than any linting tool.

Comments

Questions, corrections, and tips stay visible for everyone reading this page.

0 in thread

Join the discussion

Shown next to your comment.

Up to 4,000 characters

No comments yet

Be the first to leave a note — it helps the next reader.