datarekha
Python Medium Asked at GoogleAsked at MetaAsked at Palantir

How does Python manage memory — reference counting and the garbage collector?

The short answer

CPython uses reference counting as its primary mechanism: every object carries a counter of how many names or containers point to it, and the object is freed immediately when the count hits zero. Because reference counting cannot detect cycles, Python also runs a cyclic garbage collector that periodically finds and collects groups of objects that refer only to each other.

How to think about it

This question has two parts and interviewers want both. Part one: reference counting, which is the fast deterministic mechanism. Part two: the cyclic GC, which exists specifically because reference counting has one well-known blind spot.

Reference counting

Every Python object has an ob_refcnt field. Binding a name, appending to a list, or passing an argument increments it; the reverse decrements it. When it reaches zero, CPython calls the object’s deallocator immediately — no GC pause, no delay.

import sys

x = []
print(sys.getrefcount(x))  # 2: x + the getrefcount argument itself

y = x
print(sys.getrefcount(x))  # 3

del y
print(sys.getrefcount(x))  # back to 2

sys.getrefcount always reports one more than you expect because passing the object to the function is itself a reference.

How memory flows: a diagram

name "x"  ──►  list object  [ ob_refcnt = 2 ]
name "y"  ──►  (same object)

del y  →  ob_refcnt drops to 1
del x  →  ob_refcnt drops to 0  →  deallocator runs immediately

The cyclic GC

Reference counting fails on cycles:

a = []
b = [a]
a.append(b)   # a refs b, b refs a
del a, b      # both refcounts drop to 1, not 0 — leak without the GC

Even after del a, b, neither object can be reached from your program, but their refcounts are stuck at 1 because they point at each other. Without a second mechanism they would leak forever.

CPython’s gc module runs a tri-generational mark-and-sweep over container objects (lists, dicts, instances). Objects that survive a collection get promoted to older generations, which are collected less often — most objects die young, so this is efficient.

import gc

gc.collect()          # force a full collection
print(gc.get_count()) # (young, middle, old) object counts
gc.disable()          # safe only if you guarantee no cycles

What you can control

  • gc.disable() — worthwhile in long-running servers if you can prove no cycles; removes GC pauses.
  • __slots__ on classes — eliminates the per-instance __dict__, saving dozens of bytes per object at scale.
  • weakref — a reference that does not increment ob_refcnt, breaking cycles intentionally.
import weakref

class Node:
    def __init__(self, value):
        self.value = value

n = Node(42)
ref = weakref.ref(n)
print(ref())   # Node object
del n
print(ref())   # None — object is gone, refcount hit zero immediately
Learn it properly The GIL

Keep practising

All Python questions

Explore further

Skip to content