Skip to content

Reference counting

Python uses reference counting as its primary memory management strategy. Every object on the heap carries an internal counter, the reference count, that tracks exactly how many variables, data structures, or internal references are currently pointing to it.

The rules are simple:

  1. A new reference to an object increments the counter
  2. A reference is removed (variable deleted, goes out of scope, reassigned), the counter decrements
  3. When the counter reaches zero, no code can possibly reach the object anymore, Python immediately frees the memory
a = [1, 2, 3]
Heap
────
a ─────────────────────► [ list: 1, 2, 3 ] refcount = 1
b = a
Heap
────
a ─────────────────────► [ list: 1, 2, 3 ] refcount = 2
b ─────────────────────►
c = a
Heap
────
a ─────────────────────► [ list: 1, 2, 3 ] refcount = 3
b ─────────────────────►
c ─────────────────────►
del b
Heap
────
a ─────────────────────► [ list: 1, 2, 3 ] refcount = 2
c ─────────────────────►
del c
del a
Heap
────
[ list: 1, 2, 3 ] refcount = 0
freed immediately

Python exposes the reference count of any object via sys.getrefcount().

import sys
a = [1, 2, 3]
print(sys.getrefcount(a)) # 2 — one for a, one for getrefcount's argument
b = a
print(sys.getrefcount(a)) # 3 — a, b, getrefcount's argument
c = a
print(sys.getrefcount(a)) # 4 — a, b, c, getrefcount's argument
del b
print(sys.getrefcount(a)) # 3 — b removed, back to a, c, argument
del c
print(sys.getrefcount(a)) # 2 — c removed, back to a, argument
del a
# refcount hits 0 — object freed from memory immediately

Reference counting has two important properties:

  1. Immediacy: memory is freed the instant the last reference is removed, not at some unpredictable future time. This makes Python’s memory management deterministic and predictable for most cases.

  2. Limitation: reference counting alone cannot handle circular references, where two objects reference each other and neither ever reaches zero. Python handles this with a supplementary cyclic garbage collector that runs periodically to detect and free these cycles:

Circular reference
# A circular reference — neither object ever reaches refcount 0
a = {}
b = {}
a["b"] = b # a references b
b["a"] = a # b references a
del a
del b
# Both still have refcount 1 — neither freed by reference counting alone
# The cyclic garbage collector will find and free these

The key insight is that del a and del b only remove the stack references, the arrows from variable names to objects. The objects themselves still hold internal references to each other, keeping both counts at 1.

Reference CountingCyclic GC
TriggersEvery reference changeRuns periodically
SpeedImmediateDelayed
Handles cycles❌ No✅ Yes
OverheadPer operationBatch