Skip to content

93. Immortal Objects

PEP 683 immortal objects: _Py_IMMORTAL_REFCNT sentinel, zero-cost incref/decref, and implications for forks.

Immortal objects are objects whose lifetime is treated as effectively permanent by the CPython runtime. Their reference counts no longer behave like ordinary objects, and the interpreter avoids many normal reference counting operations on them.

Immortal objects were introduced as part of larger runtime optimization work, especially in support of free-threaded CPython and multicore scalability.

The main idea is simple:

some objects exist for the entire interpreter lifetime
incrementing and decrementing their reference counts is unnecessary
skipping refcount updates reduces synchronization overhead

This chapter examines:

why immortal objects exist
how CPython traditionally managed object lifetime
why reference counting becomes expensive
how immortality works internally
which objects become immortal
how the object header changes semantically
how immortal objects interact with free-threading
what risks and tradeoffs appear

Immortal objects change one of the oldest assumptions in CPython: that every object participates uniformly in reference counting.

93.1 Traditional Reference Counting

Historically, every CPython object maintained a mutable reference count.

Conceptually:

typedef struct {
    Py_ssize_t ob_refcnt;
    PyTypeObject *ob_type;
} PyObject;

Every new strong reference increased the count:

Py_INCREF(obj);

Every released reference decreased the count:

Py_DECREF(obj);

When the count reached zero:

object destructor executes
memory is released
contained references are decremented

This model applied to nearly every object in the runtime:

integers
strings
lists
dicts
functions
modules
types
singletons

Even globally shared objects participated fully in refcount operations.

93.2 Why Reference Counting Becomes Expensive

Reference counting is simple conceptually, but expensive at scale.

Each increment and decrement requires memory writes:

++obj->ob_refcnt;
--obj->ob_refcnt;

In traditional single-threaded execution, this overhead was acceptable.

Under multicore execution, the cost grows dramatically.

Consider:

x = None

This may appear trivial, but internally:

load reference to None
increment refcount
store pointer
later decrement refcount

Now imagine millions of threads repeatedly touching shared objects:

None
True
False
small integers
interned strings
builtin types

These objects become synchronization hotspots.

Under free-threaded execution:

multiple CPU cores modify same refcount field
cache lines bounce between cores
atomic operations serialize updates

Reference counting traffic alone can dominate execution cost.

93.3 The Core Observation

The key observation behind immortal objects is:

many runtime objects effectively never die

Examples:

None
True
False
0
1
2
""
()
int
str
list
dict
object
type

These objects typically survive until interpreter shutdown.

Therefore:

tracking exact reference counts provides little value

The runtime does not meaningfully benefit from repeatedly incrementing and decrementing them.

93.4 The Immortal Object Idea

Immortal objects use a special reference count state indicating:

this object should never be deallocated

Conceptually:

normal object
    refcount changes dynamically

immortal object
    refcount treated as permanent

Instead of:

Py_INCREF(Py_None);
Py_DECREF(Py_None);

the runtime may skip updates entirely.

This reduces:

atomic synchronization
cache contention
memory writes
cross-core coordination

especially in highly parallel execution.

93.5 Immortal Reference Counts

Immortality is represented through special reference count values.

Conceptually:

very large sentinel refcount
or
special immortal bit pattern

The runtime recognizes:

this object should never reach zero

Therefore:

Py_INCREF(obj);

can become:

if (!immortal(obj)) {
    increment_refcount(obj);
}

Similarly:

Py_DECREF(obj);

may skip decrement logic entirely.

The object effectively exits normal lifetime accounting.

93.6 Which Objects Become Immortal

Immortality is most useful for heavily shared runtime objects.

Typical candidates:

CategoryExamples
SingletonsNone, True, False
Small integers-5 through cached positive integers
Interned stringsFrequently reused identifiers
Builtin typesint, str, list, dict
Static runtime objectsGlobal runtime metadata
Permanent constantsEmpty tuple, empty bytes

These objects are:

globally shared
frequently referenced
rarely or never destroyed

They are ideal immortality candidates.

93.7 Why Small Integers Matter

Small integers are touched constantly.

Example:

for i in range(1000000):
    x = i + 1

Even simple loops repeatedly reference:

0
1
2
small counters
temporary arithmetic values

Without immortality:

refcount increments occur constantly

Under free-threaded execution:

atomic refcount traffic becomes enormous

Immortal small integers significantly reduce synchronization pressure.

93.8 The Empty Tuple Optimization

The empty tuple is another important case.

Example:

()

Many runtime operations reuse the same empty tuple object:

function defaults
empty argument tuples
internal APIs
temporary state

Without immortality:

global refcount contention appears repeatedly

Making the empty tuple immortal eliminates unnecessary updates.

93.9 Immortality and Free-Threaded CPython

Immortal objects are especially important in free-threaded CPython.

Traditional refcount updates:

++obj->ob_refcnt;

become unsafe under parallel execution.

Free-threaded CPython therefore uses atomic operations:

atomic_fetch_add(...);

Atomic operations are expensive.

Immortal objects reduce this cost dramatically:

shared singleton objects avoid atomic traffic entirely

This improves scalability across multiple CPU cores.

Without immortality, free-threaded reference counting overhead would be substantially worse.

93.10 Object Headers Still Exist

Immortal objects still use normal object structures.

Conceptually:

typedef struct {
    Py_ssize_t ob_refcnt;
    PyTypeObject *ob_type;
} PyObject;

The difference is semantic:

refcount no longer represents actual ownership count

Instead:

refcount acts as immortal sentinel state

The object header format largely remains compatible.

This is important for ABI stability.

93.11 CPython Still Uses Reference Counting

Immortal objects do not replace reference counting entirely.

Most objects remain ordinary reference-counted objects:

x = [1, 2, 3]

This list still behaves normally:

increment references
decrement references
destroy when refcount reaches zero

Immortality is selective.

The runtime applies it only where beneficial.

93.12 Why Not Make Everything Immortal

Making all objects immortal would create severe problems.

Example:

while True:
    xs = [1, 2, 3]

If lists never died:

memory usage would grow forever

Immortality works only for objects whose lifetime is already effectively permanent.

The runtime therefore distinguishes:

Object TypeLifetime
Global singletonPermanent
Temporary containerDynamic
User objectDynamic
Runtime metadataOften permanent
Frame objectDynamic

Most objects must still be reclaimed normally.

93.13 Interaction With the Garbage Collector

Immortal objects interact differently with cyclic GC.

Because they are never destroyed:

their lifetime no longer depends on refcount transitions

However:

immortal objects can still reference ordinary objects
ordinary objects can reference immortal objects

The garbage collector must still reason about graph reachability correctly.

Immortality does not eliminate GC logic.

It mainly removes unnecessary destruction tracking for permanent objects.

93.14 Destructors and Finalization

Immortal objects never reach zero references.

Therefore:

their destructors never execute

This is acceptable because immortal objects are intentionally permanent.

Examples:

None
True
False

do not require cleanup logic.

Objects needing deterministic destruction are unsuitable for immortality.

93.15 Stable Object Addresses

Immortal objects also tend to have stable addresses.

Example:

id(None)

typically remains stable throughout interpreter execution.

This benefits:

caches
interning systems
runtime metadata
fast-path comparisons
pointer identity checks

The runtime can safely assume such objects persist indefinitely.

93.16 Fast Identity Checks

Python identity checks:

x is None

depend on pointer identity.

Immortal objects strengthen assumptions around these patterns:

the object definitely survives
pointer remains valid
no destruction races occur

This becomes especially valuable under free-threaded execution.

93.17 Interned Strings and Immortality

Interned strings are another important case.

Python frequently reuses identifiers:

"name"
"value"
"append"
"dict"

Interning reduces duplication:

multiple references share same string object

These strings often persist for the interpreter lifetime.

Immortality removes unnecessary refcount traffic for such shared identifiers.

This matters because:

attribute lookup
dictionary keys
module globals
compiler metadata

all heavily use interned strings.

93.18 Immortality and Type Objects

Builtin type objects are heavily shared.

Example:

int
str
dict
list

These are accessed constantly:

instance creation
attribute lookup
type checking
slot dispatch

Without immortality:

type object refcounts become synchronization hotspots

Immortal builtin types reduce runtime contention significantly.

93.19 ABI Compatibility

One important design goal is ABI stability.

Existing extensions assume:

PyObject contains ob_refcnt

Immortal objects preserve this structure.

The meaning changes slightly:

some refcounts no longer represent exact ownership totals

but binary compatibility largely survives.

This is critical because the CPython ecosystem contains enormous amounts of native extension code.

93.20 Borrowed References Become Safer

Immortal objects indirectly improve safety for borrowed references.

Example:

PyObject *x = Py_None;

Under immortality:

object definitely survives
destruction races disappear

This reduces certain lifetime hazards in concurrent execution.

However, immortality does not solve general borrowed-reference problems for mutable objects.

93.21 Cache Coherence Effects

Immortality improves hardware-level scalability.

Without immortality:

multiple cores update same refcount field
cache line invalidation occurs constantly

This creates coherence traffic:

core A modifies cache line
core B invalidates it
core A reloads it

Immortality removes many such writes entirely.

The effect can substantially improve multicore scaling.

93.22 Performance Implications

Immortal objects improve:

AreaBenefit
Free-threaded scalabilityLess atomic contention
Cache localityFewer invalidations
Singleton accessFaster
Small integer handlingLower overhead
Type object accessReduced synchronization
Interned string reuseLower traffic

However, immortality also introduces costs:

CostReason
More runtime complexitySpecial refcount states
More conditional logicSkip checks
Less conceptual uniformityNot all objects behave equally
Potential debugging confusionRefcounts become nonliteral

The runtime trades conceptual simplicity for scalability.

93.23 Debugging Immortal Objects

Immortal objects complicate debugging.

Historically:

refcount roughly represented ownership count

With immortality:

some objects never change counts meaningfully

Tools inspecting reference counts must understand:

immortal sentinel values
special lifetime rules
nonstandard refcount semantics

Developers can no longer assume every object participates identically in memory management.

93.24 Immortality as a Runtime Optimization Layer

Immortality is fundamentally an optimization layer.

Python semantics remain unchanged:

None is None

still behaves identically.

The difference is internal:

less synchronization
fewer refcount updates
better multicore scalability

The optimization primarily exists to support modern runtime performance goals.

93.25 Future Directions

Immortal objects are part of a broader runtime evolution:

free-threaded execution
reduced global synchronization
better cache locality
scalable object lifetime management

Future runtime work may extend:

thread-local ownership
deferred refcount merging
regional allocation
object pinning
runtime specialization

Immortality is one step toward a more scalable interpreter architecture.

93.26 Chapter Summary

Immortal objects are objects whose lifetime is treated as permanent by the CPython runtime. Their reference counts no longer behave like ordinary ownership counters, and many refcount operations can be skipped entirely.

This optimization is especially important for free-threaded CPython, where atomic reference counting becomes expensive under multicore execution.

Immortal objects typically include:

singletons
small integers
builtin types
interned strings
empty containers
permanent runtime metadata

By eliminating unnecessary synchronization on heavily shared objects, immortal objects improve scalability, reduce cache contention, and support parallel interpreter execution.