Reference Counting

Reference counting is CPython’s primary memory-reclamation strategy: every object carries, in its object header, a count (ob_refcnt) of how many references currently point to it, and the instant that count drops to zero the object is destroyed and its memory reclaimed — immediately, deterministically, in the very Py_DECREF call that brought the count to zero (CPython Include/refcount.h, 3.14; Python C-API: reference counting). Creating a reference bumps the count (Py_INCREF); dropping one decrements it (Py_DECREF); reaching zero invokes the object’s type-specific destructor (tp_dealloc). This scheme gives Python deterministic destruction — an object’s __del__ and resource cleanup run as soon as the last reference disappears, not at some unpredictable later collection — but it has one fatal blind spot: it cannot reclaim reference cycles, which is exactly why CPython bolts a separate cycle-detecting collector on top (see The Cyclic Garbage Collector).

This note is the object-model overview of reference counting — what it is, the invariant it maintains, and how it shapes the rest of CPython. It is deliberately distinct from two sibling notes: Reference Counting Mechanics (the §6 memory-layer detail — how the count interacts with pymalloc, free lists, and the deallocation path) and Reference Counting in C Extensions (the §15 C-API discipline of borrowed vs owned references that extension authors must follow). Cross-link those for depth; this note teaches the concept and its consequences.

Mental Model — A Count of Pointers In, Freed at Zero

Think of each object as carrying a tally of “how many things currently hold a strong reference to me.” Every time a new name, container slot, or C variable starts referring to the object, the tally goes up by one; every time one stops, it goes down by one. When the tally hits zero, nobody can reach the object anymore, so it is safe — and immediate — to free it.

graph LR
    subgraph t0["After: x = []"]
        A0["list object<br/>ob_refcnt = 1"]
        N0["name x"] -->|strong ref| A0
    end
    subgraph t1["After: y = x"]
        A1["list object<br/>ob_refcnt = 2"]
        NX["name x"] -->|ref| A1
        NY["name y"] -->|ref| A1
    end
    subgraph t2["After: del x"]
        A2["list object<br/>ob_refcnt = 1"]
        NY2["name y"] -->|ref| A2
    end
    subgraph t3["After: del y"]
        A3["list object<br/>ob_refcnt = 0<br/>→ tp_dealloc → freed"]
    end
    t0 --> t1 --> t2 --> t3

    style A0 fill:#1f2937,stroke:#60a5fa,color:#e5e7eb
    style A1 fill:#1f2937,stroke:#60a5fa,color:#e5e7eb
    style A2 fill:#1f2937,stroke:#fbbf24,color:#e5e7eb
    style A3 fill:#374151,stroke:#f87171,color:#e5e7eb

Diagram: the lifetime of a list under reference counting. Binding a second name (y = x) raises the count to 2; deleting each name lowers it; when the second del drops it to 0, the object is destroyed in that del’s decref, not later. The insight: reclamation is tied to the act of dropping the last reference, so it is immediate and deterministic — there is no separate sweep deciding when to free a non-cyclic object.

The key word is immediate. Unlike a tracing garbage collector (Java’s, Go’s, the JVM/CLR family), which periodically scans the heap to discover unreachable objects and free them at a time of its choosing, reference counting frees an object at the exact program point where its last reference goes away. This determinism is a defining property of CPython’s behavior and the reason idioms like “the file closes when the open() result goes out of scope” work at all (though relying on it is discouraged — see Failure Modes).

Mechanical Walk-through — INCREF, DECREF, and the Zero Trigger

Incrementing: Py_INCREF

Taking a new strong reference to an object calls Py_INCREF(op), documented as “Indicate taking a new strong reference to object o, indicating it is in use and should not be destroyed” (C-API: refcounting). In the default (GIL-enabled) 64-bit build, the inline body is essentially (refcount.h, 3.14, lines 281–294):

PY_UINT32_T cur_refcnt = op->ob_refcnt;
if (cur_refcnt >= _Py_IMMORTAL_INITIAL_REFCNT) {
    return;                       // immortal object → no-op
}
op->ob_refcnt = cur_refcnt + 1;   // otherwise just add one

Two things to notice. First, the operation is not atomic in this build — a plain read, compare, increment, write — which is sound precisely because the GIL guarantees only one thread mutates a refcount at a time. The cheapness of this non-atomic increment is a major reason the GIL exists at all (see below). Second, the leading immortal check: if the count is already at or above the immortal threshold, Py_INCREF does nothing — immortal objects (see Immortal Objects) never change their count.

Decrementing: Py_DECREF and the deallocation trigger

Releasing a strong reference calls Py_DECREF(op): “Release a strong reference to object o, indicating the reference is no longer used. Once the last strong reference is released (i.e. the object’s reference count reaches 0), the object’s type’s deallocation function (which must not be NULL) is invoked” (C-API: refcounting). The default-build inline body is exactly (refcount.h, 3.14, lines ~360–375):

static inline void Py_DECREF(PyObject *op)
{
    if (_Py_IsImmortal(op)) {
        return;                   // immortal → no-op
    }
    if (--op->ob_refcnt == 0) {
        _Py_Dealloc(op);          // last reference gone → destroy now
    }
}

This is the heart of the whole scheme. Decrement; if the result is zero, call _Py_Dealloc, which reads the object’s type and invokes its tp_dealloc slot — the type-specific destructor. tp_dealloc is responsible for releasing whatever the object owns: for a container, it Py_DECREFs each contained object (which can cascade, freeing a whole subtree); for an object with a __del__ finalizer, it runs that Python code; finally it frees the object’s own memory back to the allocator. The object.h comment makes the contract explicit: “The Py_DECREF() macro uses the tp_dealloc method without checking for a nil pointer; it should always be implemented except if … the reference count will never reach zero” (object.h, 3.14).

The invariant

The single invariant reference counting maintains is: ob_refcnt equals the number of live strong references to the object, and the object exists if and only if that number is positive. Every reference-creating event must be paired with an eventual reference-dropping event. In pure-Python code the interpreter does this bookkeeping for you automatically — the bytecode that stores into a variable, appends to a list, or passes an argument emits the appropriate INCREF/DECREF, and LOAD_FAST/STORE_FAST etc. keep counts correct. In C extension code you must do it by hand, which is the entire subject of Reference Counting in C Extensions — getting a single INCREF/DECREF wrong there is the classic extension bug (a missing DECREF leaks; an extra DECREF frees a still-referenced object, corrupting memory).

The X-variants and convenience helpers

The C-API provides a family around the two core operations (C-API: refcounting):

  • Py_XINCREF / Py_XDECREF — identical to the plain versions but tolerate a NULL pointer (do nothing). The plain Py_INCREF/Py_DECREF require a non-NULL argument; passing NULL is undefined.
  • Py_NewRef(o) / Py_XNewRef(o) — call Py_INCREF on o and return o, so you can write self->attr = Py_NewRef(obj); instead of a separate INCREF then assignment.
  • Py_CLEAR(o) — release a reference and set the pointer to NULL, carefully using a temporary so the field is NULL before the decref runs. The docs recommend it “whenever releasing a reference to an object that might be traversed during garbage collection” — because if the destructor cascade re-enters and looks at the half-cleared field, it must not see a dangling pointer.

Deterministic Destruction vs Tracing GC

The property that most distinguishes CPython from other managed runtimes is deterministic, eager destruction. Because an object dies the moment its last reference drops, three things follow that a tracing collector does not give you:

  1. Prompt resource release. When a local variable holding the only reference to a file, socket, or lock goes out of scope at the end of a function, the object is freed then, running its cleanup then. Programs can (and historically did) lean on this so that f = open(...) followed by losing the reference closes the file deterministically.

  2. No global pauses for non-cyclic garbage. Acyclic garbage never accumulates waiting for a collector; it is freed incrementally as the program runs. The classic “stop-the-world GC pause” does not apply to reference-counted reclamation (the cyclic collector can still pause — see The Cyclic Garbage Collector).

  3. Predictable memory profile for straight-line code. Memory is returned as references are dropped, so a loop that creates and discards objects does not grow the heap waiting for a sweep.

The cost is that every reference operation is work at runtime (the INCREF/DECREF on every assignment, argument pass, and return) and that the count must be maintained perfectly. A tracing collector pays nothing per-assignment and tolerates cycles trivially, but gives up determinism and pays in collection pauses. CPython chose refcounting-plus-a-cycle-collector; PyPy chose a tracing collector and consequently does not promise deterministic destruction — relying on prompt __del__ is non-portable across implementations, which the data model documentation warns about (Python data model). See CPython vs PyPy and Alternative Implementations.

What Reference Counting Cannot Do — Cycles

Reference counting has one structural failure: it cannot reclaim reference cycles. If object A holds a reference to B and B holds a reference back to A, then even after every external reference to the pair is dropped, A’s count is still ≥1 (because B points at it) and B’s is still ≥1 (because A points at it). Neither count ever reaches zero, so neither is ever freed by refcounting alone — the memory leaks.

a = {}
b = {}
a["b"] = b        # a → b
b["a"] = a        # b → a   (now a cycle)
del a             # external ref to the first dict gone; its count is still 1 (b points at it)
del b             # external ref to the second gone; its count is still 1 (a points at it)
# Neither dict's refcount is 0. Refcounting will NEVER free them.

The simplest self-cycle is lst = []; lst.append(lst) — a list containing itself. After del lst, the list’s count is still 1 (it references itself), so refcounting cannot free it. This unreclaimable-cycle problem is the entire reason CPython adds a second mechanism: a generational, cycle-detecting garbage collector that periodically finds groups of objects reachable only from each other and collects them. That collector — how it detects cycles by subtracting internal references, the generational hypothesis, finalization order — is the subject of The Cyclic Garbage Collector, Reference Cycles and How They Form, and §7 of the Python Internals MOC. The two systems are complementary: refcounting handles the common acyclic case immediately and cheaply; the cyclic collector handles the rare leak refcounting leaves behind.

Immortal Objects — Opting Out of the Count

Some objects are referenced so pervasively and live so long that counting their references is pure overhead — None, True, False, the small integers, interned strings, and the built-in type objects are touched constantly across the whole program (and, with per-interpreter GIL and free-threading, across threads and interpreters). PEP 683, shipped in Python 3.12, introduced immortal objects: objects whose refcount is pinned at a sentinel value so that Py_INCREF and Py_DECREF become no-ops on them and they are never freed (PEP 683; Immortal Objects).

Mechanically, in 3.14 an object is immortal when the sign bit of its 32-bit ob_refcnt is set: _Py_IsImmortal returns (int32_t)op->ob_refcnt < 0 on the default 64-bit build (refcount.h @ v3.14.5, lines 125–135). Both Py_INCREF (via the cur_refcnt >= _Py_IMMORTAL_INITIAL_REFCNT check) and Py_DECREF (via _Py_IsImmortal) detect this and return early without touching the count. The benefit beyond saved cycles is correctness under parallelism: if an object’s refcount never changes, multiple threads or interpreters can share it with no synchronization on the count — which is why immortality is foundational to both the per-interpreter GIL and free-threading. The docs note the consequence for introspection: Py_REFCNT on an immortal object returns “a very high refcount that does not reflect the actual number of references … do not rely on the returned value to be accurate, other than a value of 0 or 1” (C-API: refcounting).

PEP vs shipped source (verified 2026-06-01)

PEP 683 (3.12) described immortality as “the two topmost bits set” near 2⁶², but the shipped source uses a refined 32-bit-field scheme. Verified against refcount.h @ v3.14.5, lines 49–50 and 125–135: _Py_IMMORTAL_INITIAL_REFCNT = (3ULL << 30) and _Py_IMMORTAL_MINIMUM_REFCNT = (1ULL << 31), with immortality detected via the sign of the low 32 bits. The PEP gives the original rationale; refcount.h at the release tag is authoritative for current values. Full detail in Immortal Objects.

Reference Counting and the GIL

There is a tight, often-missed connection between reference counting and the Global Interpreter Lock. Because refcounting requires mutating ob_refcnt on essentially every object operation, and because those objects are shared across threads, the increments and decrements would have to be atomic (or otherwise synchronized) in a multithreaded interpreter — atomic operations that are markedly slower than a plain read-modify-write. The GIL sidesteps this entirely: since only one thread executes Python bytecode at a time, refcount mutations need no atomics, and Py_INCREF/Py_DECREF stay as cheap as the two-line inline functions shown above. In other words, cheap refcounting is one of the main things the GIL buys, and conversely the cost of refcounting is one of the main reasons removing the GIL was hard. The free-threaded build (PEP 703) had to re-engineer refcounting to be correct without the GIL — using biased reference counting (a fast thread-local count for the owning thread, an atomic shared count for others) and deferred reference counting for certain frequently-shared objects, plus immortalization of the worst offenders. This is the through-line of §8 of the Python Internals MOC.

Failure Modes and Misunderstandings

  • The Py_DECREF destructor can run arbitrary code. Because dropping the last reference invokes tp_dealloc, which for an object with __del__ runs Python code, a Py_DECREF is a potential re-entry point into the interpreter. The docs warn: “The deallocation function can cause arbitrary Python code to be invoked … any object that is reachable from a global variable should be in a consistent state before Py_DECREF() is invoked” (C-API: refcounting). The canonical safe pattern is to remove the object from a data structure first, then decref a temporary — which is precisely what Py_CLEAR automates.

  • The pointer is dangling after the decref that frees the object. Once Py_DECREF has driven the count to zero and freed the object, the PyObject * you held is invalid; using it is a use-after-free. (Py_CLEAR exists partly to null it out.)

  • Do not rely on __del__ timing. While CPython’s refcounting does free objects promptly, the language does not guarantee it — other implementations (PyPy) use tracing GC and finalize lazily or not at all. The data model docs caution that __del__ may not be called and its timing is implementation-defined. Use with/context managers (The Context Manager Protocol) for deterministic resource cleanup, not the refcounter.

  • Cycles silently leak under refcounting alone. A cycle of objects with no external references will never be freed by refcounting; only the cyclic GC reclaims them, and only on its schedule — and if any object in the cycle has a __del__, finalization-order issues historically complicated collection (see Finalizers and the del Method).

  • The count you read may be inflated by the reader itself. Calling sys.getrefcount(obj) reports a count one higher than you might expect, because passing obj as the argument creates a temporary reference. And for immortal objects (None, small ints, True/False) the number is a meaningless sentinel.

See Also