Python/Python Internals/Reference Counting & Garbage Collection

Reference Counting & Garbage Collection

CPython frees most objects the instant their last reference goes away via reference counting, and a generational cyclic garbage collector mops up the reference cycles that counting alone can never reclaim.

Advanced35mInterview Production

Learning objectives

  • Explain how reference counting frees an object the moment its last reference disappears
  • Show why reference cycles defeat refcounting and how the generational cyclic collector reclaims them
  • Use sys.getrefcount, the gc module, and weakref to observe and control object lifetime
  • Answer the interview question 'how does Python manage memory' with refcounting + cyclic GC + pymalloc

Prerequisites

Variables & referencesObjects and identity (id, is)The CPython object model

Version notes

  • · Since Python 3.4 (PEP 442) the cyclic collector can finalize objects with __del__ that are part of a cycle; before that, such cycles were considered uncollectable and leaked into gc.garbage.
  • · The collector is generational and tunable via gc.set_threshold()/gc.get_threshold() — but the exact generation count and default thresholds (classically (700, 10, 10)) are implementation details that have changed across versions (recent CPython uses an incremental collector), so never depend on the specific numbers.
  • · The refcount field's representation has evolved — e.g. immortal objects in 3.12 (PEP 683) carry a saturated count — which is one more reason never to hard-code exact sys.getrefcount values.

Simple explanation

What it is. CPython manages memory in two cooperating layers. Every object carries a reference count, and when that count drops to zero the object is freed immediately. A separate generational garbage collector runs periodically to find and free reference cycles that counting alone can never reach.

Why it exists. Reference counting reclaims memory promptly and (mostly) deterministically — the moment nothing points at an object, it's gone — which keeps memory tight without stop-the-world pauses. Its one blind spot is objects that point at each other: they keep each other's count above zero forever, so a second mechanism is needed to catch those cycles.

The problem it solves. Together they give low-latency, largely deterministic memory management with no manual free() calls: refcounting handles the overwhelmingly common non-cyclic case instantly, and the cyclic GC stops reference cycles from leaking.

How it works. Binding a name, appending to a container, or passing an argument increments an object's refcount (Py_INCREF); rebinding, del, or leaving a scope decrements it (Py_DECREF). At zero, CPython runs the object's deallocator and reclaims the memory — often back into pymalloc's internal pools rather than to the operating system. Alongside this, the cyclic collector tracks container objects across three generations; younger generations are scanned far more often, and it identifies groups of objects that are only reachable from within their own group and frees them.

Analogy: Reference counting is a nightclub with a counter on the door: every entry adds one, every exit subtracts one, and when the count hits zero the lights switch off automatically. But two friends who each insist on staying 'as long as the other stays' would keep the count above zero forever — so a manager (the cyclic GC) walks the room now and then and sends home any group that's only keeping itself there.

Common misconceptions

  • "Python frees memory with a garbage collector, like Java." — Mostly it's reference counting doing the work instantly; the cyclic GC only exists to handle cycles.
  • "sys.getrefcount tells me exactly how many references exist." — It reports one extra: the temporary reference held by the argument passed into getrefcount itself.
  • "gc.disable() turns off all memory reclamation." — Refcounting still frees every non-cyclic object; only cyclic garbage accumulates while the collector is off.
  • "__del__ is a reliable C++-style destructor." — Its timing depends on refcounts, and for objects trapped in cycles it runs only when the cyclic collector reaches them (and historically, before 3.4, not at all).

Common alternatives

  • weakref / WeakValueDictionary — reference objects without keeping them alive, sidestepping cycles and leaks
  • context managers (with) — deterministic resource cleanup instead of relying on __del__ timing
  • gc.disable() plus manual gc.collect() — trade automatic cycle collection for latency control in hot paths
  • tracemalloc / objgraph — diagnose leaks and inspect what is retaining an object

Interactive visualization

Step 1 / 5
1a = [1, 2, 3] # new list object
2b = a # another name, same object
3del a # drop one reference
4b = None # drop the last reference

Heap / objects

Llist

[1, 2, 3]

refcount: 1

References

a L

Create the object

The list is created and the name a points at it. Its reference count is 1.

Code examples

BeginnerReference counts rise and fall
python
Loading editor…

Note: Absolute refcounts are environment-dependent, so we only ever compare relative changes. The '+1' from getrefcount's own argument cancels out between the two calls.

Behind the scenes

  • Every PyObject begins with an ob_refcnt field; Py_INCREF/Py_DECREF adjust it, and reaching zero calls the type's tp_dealloc to free the object right away.
  • The GIL exists partly because these refcount updates are not atomic — two threads mutating the same count without a lock would corrupt it and leak or double-free memory.
  • sys.getrefcount(x) always reports one more than you'd expect, because passing x into the function creates a temporary reference that lives for the duration of the call.
  • The cyclic collector only tracks container types (objects that can reference others — lists, dicts, instances, tuples); atomic objects like ints and strs can't form cycles and are never scanned.
  • Objects start in the youngest generation; those that survive a collection are promoted to older generations that are scanned less often (classic default thresholds were (700, 10, 10), tunable via gc.set_threshold()).
  • Freed small objects usually return to pymalloc's arena/pool free-lists rather than to the OS, so a process's memory footprint often stays high even after a big allocation is released.

Interview insights

Key definitions

  • · Reference count: a per-object integer tracking how many references point to it; the object is freed the moment it reaches zero.
  • · Reference cycle: a group of objects that reference each other, keeping every count above zero even when nothing outside the group can reach them.
  • · Generational GC: a collector that groups objects into generations and scans younger ones more frequently, exploiting the observation that most objects die young.
How does Python manage memory?

Concise answer: Primarily by reference counting: every object tracks how many references point to it and is freed the instant that hits zero. A generational cyclic garbage collector runs alongside to reclaim reference cycles that counting can't, and small objects are served from pymalloc's pools.

Detailed answer: CPython's main mechanism is reference counting — Py_INCREF/Py_DECREF on binding and unbinding, with immediate deallocation at zero, giving prompt and largely deterministic cleanup. Its blind spot is cycles (A -> B -> A), whose counts never reach zero; the optional generational garbage collector (the gc module, three generations) periodically finds and frees unreachable cycles. Beneath both, the pymalloc allocator manages small objects in arenas and pools to avoid constant malloc/free calls. Practical upshots: cleanup is usually immediate, but don't rely on __del__ timing for objects that might be in cycles — use context managers for resources and weakref to avoid cycles.

Common mistake: Saying Python uses only a Java-style tracing garbage collector — reference counting actually does the bulk of the work.

Follow-ups:

  • · What can reference counting NOT free?
  • · Why is __del__ timing risky?
  • · How does the GIL relate to refcounts?
What is a reference cycle, and how does CPython deal with it?

Concise answer: Objects that reference each other keep their counts above zero, so refcounting never frees them. The generational cyclic GC detects groups reachable only from within themselves and collects them.

Detailed answer: If a.other = b and b.other = a, each object holds a reference to the other, so deleting the outside names still leaves both counts at 1 — refcounting is stuck. CPython's cyclic collector periodically scans tracked container objects, works out which are unreachable from outside their own group, and frees them. Since 3.4 it can even run __del__ on objects in cycles (before that, such objects leaked into gc.garbage). You can trigger a pass with gc.collect(), and avoid cycles proactively with weakref.

Common mistake: Assuming del always frees an object immediately — it doesn't when the object is part of a cycle.

Follow-ups:

  • · How would you break the cycle by design?
  • · Which kinds of objects can't form cycles at all?
Why does sys.getrefcount return a number one higher than expected, and why shouldn't you assert on exact values?

Concise answer: Passing the object into getrefcount creates one temporary reference, so the reported count includes it. Exact values also depend on interpreter internals (interning, caching, immortal objects), so they vary across versions and machines.

Detailed answer: getrefcount(x) receives x as an argument, which is itself a reference alive for the duration of the call, so the reported number is always one more than the count of references outside the call. Beyond that, CPython interns small ints and some strings, caches constants, and (since 3.12) marks certain objects immortal with a saturated count — so absolute figures aren't portable. Reason about refcounting with relative changes (before vs after) or with weakref liveness, never with hard-coded counts.

Common mistake: Hard-coding an expected refcount in a test and then watching it break on a different Python build.

Follow-ups:

  • · What are immortal objects (PEP 683)?
  • · How else can you observe an object's lifetime? (weakref)

Knowledge check

Multiple choiceQuestion 1 of 3

An object's reference count drops to zero. What happens?

Multiple choiceQuestion 2 of 3

Which situation can reference counting alone NOT clean up?

What's the output?Question 3 of 3

What does this print?

python
import sys
x = object()
before = sys.getrefcount(x)
y = x
print(sys.getrefcount(x) - before)

Score: 0/3