If you’ve ever printed id(obj)
while debugging and wondered, “What exactly is this number?”, this post is for you. We’ll unpack how objects live in memory, what id()
returns, and how it helps you reason about Python behavior—without getting lost in jargon.
id(obj)
returns a unique identity for the object during its lifetime.- In CPython (the default Python), this is the memory address where the object’s header lives.
- Same
id
⇒ same object (same memory). Same value does not imply sameid
. - Immutable types (like small ints, some strings) may share objects due to caching/interning.
What exactly is returned by id()
?
- In CPython,
id(obj)
is the address in RAM of the object’s header (technically, a pointer cast to an integer). - In other Python implementations (e.g., PyPy),
id()
is still a unique identifier, but not guaranteed to be a raw memory address.
x = 42
print(id(x)) # e.g., 140709568352400
print(hex(id(x))) # e.g., 0x7ffef9a8b2a0 (hex address form)
Think of
id()
like the flat number of an apartment. The furniture (data) can be identical in two apartments, but their flat numbers (ids) differ.
How objects are stored in memory (CPython view)
Every Python object has a C-level header with (simplified):
- Reference count (
ob_refcnt
) – how many active references point to it - Type pointer (
ob_type
) – tells Python what kind of object it is (int, list, str, …)
So when you do:
x = 10
Python allocates (or reuses) an integer object and x
holds a reference to it.id(x)
is the address of that object’s header.
Equality vs Identity: ==
vs is
==
checks value equality (same content)is
checks identity (same object in memory)
a = [1, 2, 3]
b = [1, 2, 3]
c = a
print(a == b) # True (same contents)
print(a is b) # False (different lists)
print(a is c) # True (same list object)
You can confirm with id()
:
print(id(a), id(b), id(c))
# a and c share the same id; b has a different id
Small integer caching & string interning
Python performs some clever optimizations.
Small integer caching (CPython)
- Integers in the range [-5, 256] are pre-allocated and reused.
- So two variables pointing to the number
10
often share the same object.
x = 10
y = 10
print(x is y) # True in CPython
print(id(x) == id(y)) # True
Larger integers may or may not be reused depending on the code path:
x = 10_000
y = 10_000
print(x is y) # Often False; depends on the runtime and context
String interning
- Short, identifier-like strings (e.g.,
'hello'
,'abc123'
) are often interned (reused). - Dynamically created strings may not be interned unless you use
sys.intern()
.
s1 = "hello_world"
s2 = "hello_world"
print(s1 is s2) # Often True (interned)
s3 = "".join(["hello", "_world"])
print(s3 is s2) # Often False (built at runtime)
Mutable vs immutable: why id()
behaves differently
- Immutable objects (ints, strs, tuples) can be shared safely; Python may reuse them.
- Mutable objects (lists, dicts, sets) are distinct by default when created anew.
a = [1, 2]
b = [1, 2]
print(id(a) == id(b)) # False (two separate lists)
c = a
print(id(a) == id(c)) # True (aliasing the same list)
To make a copy (different id
), don’t alias:
a = [1, 2, 3]
b = a.copy() # or b = list(a) or b = a[:]
print(a is b) # False
Can id()
be reused?
Yes—after an object is destroyed, Python may allocate a new object at the same memory address (hence the same id
later). This is why id()
is only guaranteed to be unique during the object’s lifetime.
def demo():
x = 999999
print("inside:", id(x))
demo()
y = 999999
print("outside:", id(y))
# The numbers may or may not match depending on reuse; don’t rely on it.
Practical debugging uses
- Check aliasing (shared references)
“Are these two variables pointing to the same object?”a = {'count': 1} b = a print(a is b) # True
- Spot unintended mutations
If you expected a copy but see the sameid
, you’re mutating the original object. - Understand performance quirks
Seeing identicalid
s for small ints/strings? That’s caching/interning, not a bug.
Real-life mental model
- Apartment (object) has a flat number (id) and furniture (value).
- Two apartments can look the same inside (equal contents), but the flat numbers differ.
- Two keys to the same apartment = multiple variables referencing one object.
Code you can run to “see” memory identities
def show(label, obj):
print(f"{label}: value={obj!r} id={hex(id(obj))}")
# Small ints (often same id due to caching)
x, y = 10, 10
show("x", x)
show("y", y)
# Larger ints (may not share id)
p, q = 10_000, 10_000
show("p", p)
show("q", q)
# Lists: distinct objects
a, b = [1, 2, 3], [1, 2, 3]
show("a", a)
show("b", b)
print("a is b:", a is b)
# Aliasing
c = a
show("c (alias of a)", c)
print("a is c:", a is c)
Common pitfalls to avoid
- Don’t use
is
for value comparison. Use==
for content andis
for identity. - Don’t rely on
id()
for ordering or persistence—it’s an implementation detail. - Don’t assume
id()
is always a memory address outside CPython (e.g., PyPy).
Key takeaways
id()
is your window into object identity—great for debugging references.- Same
id
⇒ same object. Same value does not imply sameid
. - CPython may reuse objects (small ints, interned strings); mutable containers are usually distinct unless explicitly aliased.
FAQ
Q: Why do two variables with 10
have the same id
?
Because CPython caches small integers. It’s an optimization.
Q: Why does a is b
sometimes return True
for strings?
They may be interned (reused). It’s normal for short, identifier-like strings.
Q: Can I treat id()
as a true memory address?
In CPython yes (practically); in general Python terms, consider it a unique identity, not a guaranteed address.