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 veryPy_DECREFcall that brought the count to zero (CPythonInclude/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 oneTwo 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 aNULLpointer (do nothing). The plainPy_INCREF/Py_DECREFrequire a non-NULLargument; passingNULLis undefined.Py_NewRef(o)/Py_XNewRef(o)— callPy_INCREFonoand returno, so you can writeself->attr = Py_NewRef(obj);instead of a separate INCREF then assignment.Py_CLEAR(o)— release a reference and set the pointer toNULL, carefully using a temporary so the field isNULLbefore 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:
-
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. -
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).
-
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.hat 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_DECREFdestructor can run arbitrary code. Because dropping the last reference invokestp_dealloc, which for an object with__del__runs Python code, aPy_DECREFis 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 beforePy_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 whatPy_CLEARautomates. -
The pointer is dangling after the decref that frees the object. Once
Py_DECREFhas driven the count to zero and freed the object, thePyObject *you held is invalid; using it is a use-after-free. (Py_CLEARexists 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. Usewith/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 passingobjas the argument creates a temporary reference. And for immortal objects (None, small ints,True/False) the number is a meaningless sentinel.
See Also
- PyObject and the Object Header — where
ob_refcntlives; the companion cornerstone to this note. - Reference Counting Mechanics — §6 memory-layer detail: how decref interacts with pymalloc, free lists, and the dealloc path. (Distinct lens — this note is the overview.)
- Reference Counting in C Extensions — §15 C-API discipline: borrowed vs owned references, the rules extension authors must follow.
- The Cyclic Garbage Collector — the cycle-detecting collector that handles what refcounting cannot.
- Reference Cycles and How They Form — why and where cycles arise.
- Immortal Objects — objects that opt out of the count (PEP 683, 3.12).
- The Global Interpreter Lock — why cheap (non-atomic) refcounting depends on it.
- Free-Threaded CPython, Biased Reference Counting, Deferred Reference Counting — refcounting re-engineered without the GIL (§8).
- Finalizers and the del Method, The Context Manager Protocol — finalization and deterministic cleanup.
- Python Internals MOC — §4 “The Object Model”; the “Reference counting is the spine” cross-cutting theme.