How Python Uses Reference Counting to Free Memory

Python manages memory automatically primarily through reference counting, a deterministic mechanism where the runtime tracks how many aliases or references point to an object. Each time an object is referenced, its internal counter increments; when a reference is removed, the counter decrements. The moment an object's reference counter hits zero, its memory is immediately deallocated and returned to the Python memory pool or the operating system, ensuring predictable and low-latency cleanup for the majority of runtime allocations.

The Reference Counter: ob_refcnt

At the C level (CPython), every object is represented by a structure based on PyObject. Inside this base structure exists a field named ob_refcnt. This integer field stores the current total number of active references to that specific object in memory.

How References Increase

An object’s ob_refcnt is incremented automatically in several common scenarios:

How References Decrease

An object’s ob_refcnt is decremented when any existing reference is broken:

The Deallocation Process

The defining feature of reference counting is immediate reclamation:

  1. As soon as an operation causes ob_refcnt to transition from 1 to 0, Python invokes the type-specific deallocator (tp_dealloc) defined in the object's type structure.
  2. The deallocator decrements the reference counts of any other objects that this object referenced.
  3. The memory block occupied by the object is cleared and marked as available for future allocations within Python's internal memory manager (PyMalloc) or returned to the system heap.

Because deallocation happens immediately upon the last reference being lost, objects often release associated system resources (such as file handles or network sockets) without waiting for a scheduled collection phase.

Circular References and the Cyclic Garbage Collector

While reference counting is fast and predictable, it cannot reclaim memory involved in circular references on its own. A reference cycle occurs when two or more objects reference each other (e.g., object A points to object B, and object B points to object A), but neither object can be reached from anywhere else in the application.

In this scenario, both objects maintain an ob_refcnt of at least 1, preventing standard reference counting from ever reclaiming them. To solve this, Python supplements reference counting with a secondary, generational cyclic garbage collector. This collector runs periodically, detects self-referential groups that are isolated from root scopes, breaks the cycles, and forces the deallocation of the unreachable objects.