backend / python core / tricky questions / 14_id_reuse_after_gc.md

id() reuse after garbage collection

1 min read source

id() reuse after garbage collection

The gotcha

id() returns a number unique to an object for the lifetime of that object. Once the object is garbage collected, the same id can be reused for a different object. So caching id()s and comparing later is unsafe.

Minimal repro

class Thing: pass

a = Thing()
print(id(a))   # e.g. 140234567890

a = None        # original Thing eligible for GC

b = Thing()
print(id(b))    # often the SAME number — reused

# So this trick is broken:
def is_same(x, cached_id):
    return id(x) == cached_id   # false positives possible

In CPython, id(x) is the memory address of the object. After del/refcount→0, the slab is freed and may be reallocated.

Why it happens

CPython documents id() as “unique among simultaneously existing objects.” When refcount drops to zero, the object is destroyed and its address freed back to the allocator. A subsequent allocation of the same size class (CPython has small-object pools) often reuses that exact address.

Other Python implementations (PyPy, Jython) define id() differently, sometimes generating logical IDs that don’t reuse — but CPython is the dominant case.

How to avoid

Don’t rely on id() for tracking objects across time. If you need to “remember” an object, hold a reference to it (or a weakref):

import weakref

ref = weakref.ref(my_object)
# later:
if ref() is None:
    print("collected")

For caching by identity (rare), use WeakValueDictionary or WeakSet.

For “is this the same object I had before?” use is, but only while you still hold a strong reference.

Interview angle

“Two objects with the same id() are necessarily the same object — true or false?” The unwary answer “true” is wrong: only true at the same point in time. Follow-up: “How would you safely track ‘is this the same instance I saw before?’”