Understanding Python's id() Function
python id function: Explore how Python's id() function returns object identity, how CPython assigns memory addresses, and when relying on id() is safe or misleading.
python id function requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
The built-in id() function in Python returns an integer that serves as a stable identity token for an object during its lifetime. In CPython, the reference implementation, that integer is the object's memory address. The function exists so you can ask "is this the same object as that one?" without relying on equality. This distinction matters because two objects can be equal in value while occupying different memory locations, and id() lets you detect that difference.
What id() Actually Returns
When you call id(obj), Python returns an integer that is guaranteed to be unique among objects that exist at the same time. The value is constant for the lifetime of the object. Once the object is garbage-collected, its identity may be reused by a new object. The documentation does not promise that the integer is a memory address; that is an implementation detail of CPython. Other Python interpreters, such as PyPy or Jython, may assign identity differently, but they still honor the uniqueness guarantee for live objects.
x = [1, 2, 3] print(id(x)) # e.g., 140277123456768
The exact number is rarely useful on its own. What matters is that two variables pointing to the same object produce the same id, while two distinct objects produce different ids.
How CPython Assigns Object Identity
CPython stores every object in memory at a specific address. The id() function simply casts that pointer to an integer. This is why the returned value changes between runs of the same script: the operating system decides where the object lands in virtual memory. Because of this, you cannot rely on a specific id value being stable across program executions.
The address is determined when the object is allocated. For small integers, CPython caches objects for values from -5 to 256, so all references to 5 point to the same preallocated object. This is an implementation detail that can surprise developers who assume every literal creates a new object.
a = 5 b = 5 print(id(a) == id(b)) # True, because small integers are interned c = 257 d = 257 print(id(c) == id(d)) # False in CPython, but not guaranteed
Using id() to Compare Object Identity
The most direct use of id() is to check whether two variables reference the same object. This is equivalent to the is operator, which is the idiomatic way to perform identity comparison in Python.
class Node: def __init__(self, value): self.value = value first = Node(10) second = first third = Node(10) print(id(first) == id(second)) # True print(id(first) == id(third)) # False print(first is second) # True print(first is third) # False
Using id() for identity checks works, but is is clearer and faster because it directly compares object pointers without an extra function call. Prefer is in normal code. id() becomes useful when you need to store identity as a value, such as in a dictionary mapping objects to metadata.
Why id() Can Be Misleading After Garbage Collection
The identity of an object is only guaranteed while the object is alive. Once all references are gone, Python's garbage collector may reclaim the memory, and a new object can be allocated at the same address. This means the same id value can refer to different objects over time.
class Temp: pass first = Temp() first_id = id(first) del first second = Temp() second_id = id(second) print(first_id == second_id) # Possible, but not deterministic
This behavior makes id() unsuitable as a persistent key in a cache or registry. If you store an id and later look it up, you might retrieve metadata for a completely different object that was allocated at the same address. Use weakref for caching when you need to associate data with an object without preventing its garbage collection.
id(), is, and ==: Choosing the Right Comparison
Developers often confuse identity and equality. The == operator compares values, while is compares identity. id() is the underlying mechanism for identity, but is is the preferred surface syntax. The table below summarizes the differences.
| Comparison method | Checks | Typical use |
|---|---|---|
== | Value equality | Comparing contents of numbers, strings, collections |
is | Object identity | Comparing against None, checking singleton objects |
id() | Identity as integer | Debugging, storing identity in a data structure |
A common mistake is using is for value comparison, which works for small integers due to interning but fails for larger values or custom objects. Similarly, using id() when you mean equality leads to confusing behavior. For most code, == is correct; use is only when you explicitly need to know whether two names point to the same object.
When Relying on id() Is Safe
There are a few scenarios where id() is genuinely useful. Debugging is one: printing id(obj) can help you confirm that a variable is being passed around without accidental copying. Another is implementing a custom identity map for the lifetime of a process, provided you keep strong references to the objects so their ids cannot be reused. For example, a registry that stores objects in a dictionary keyed by id() works as long as the dictionary itself holds a reference to each object, keeping it alive.
registry = {} def register(obj): registry[id(obj)] = obj
This pattern is safe because the object is stored as a value in the same dictionary, so its id cannot be recycled. However, if you later remove the object from the registry, the id becomes invalid for future lookups.
Performance and Memory Considerations
Calling id() is a cheap operation in CPython because it simply returns the object's pointer cast to an integer. It does not allocate new memory or traverse the object graph. The overhead is comparable to a function call, which is negligible in most contexts. The real performance risk comes from using id() as a dictionary key without holding a reference. This can cause subtle memory leaks or incorrect behavior because the key may be reused after garbage collection.
If you need to associate metadata with objects that may be garbage-collected, use weakref.WeakKeyDictionary instead. It keys on the object itself and automatically removes entries when the object is collected, avoiding both memory leaks and identity reuse issues. For cases where you need a stable identifier across process restarts, id() is not suitable; use a UUID or a database-generated key.
A final caution: do not serialize id() values to disk or send them over the network. They are only meaningful within a single process and can change between runs. Treat id() as a runtime debugging aid and an implementation detail, not as a public API for identity management.