Gyaan

Garbage Collection and Reference Counting

intermediate garbage-collection reference-counting memory gc

Python manages memory automatically. We create objects, use them, and Python cleans them up when they’re no longer needed. The primary mechanism is reference counting, backed by a cyclic garbage collector for edge cases.

Reference Counting

Every object in Python has a reference count — the number of names or containers pointing to it. When that count drops to zero, Python immediately frees the memory.

Reference Counting in Action
a = [1, 2]
[1, 2] refs: 1
b = a
[1, 2] refs: 2
del a
[1, 2] refs: 1
del b
[1, 2] refs: 0 → freed!

We can check an object’s reference count with sys.getrefcount():

import sys

a = [1, 2, 3]
print(sys.getrefcount(a))  # 2 (one for 'a', one for the argument to getrefcount)

b = a
print(sys.getrefcount(a))  # 3

del b
print(sys.getrefcount(a))  # 2 again

Note: getrefcount() always shows one extra because passing the object to the function temporarily creates another reference.

What Increases the Count?

  • Assigning to a variable: a = obj
  • Adding to a container: my_list.append(obj)
  • Passing as a function argument
  • Creating an alias: b = a

What decreases it: del a, reassigning a = something_else, or the variable going out of scope.

The Circular Reference Problem

Reference counting alone can’t handle this:

class Node:
    def __init__(self):
        self.ref = None

a = Node()
b = Node()
a.ref = b   # a points to b
b.ref = a   # b points to a — circular!

del a
del b
# Both refcounts are still 1 (they reference each other)
# Reference counting alone can't free them!

The Generational Garbage Collector

Python’s GC handles circular references using a generational approach. Objects are grouped into three generations:

  • Generation 0 — newly created objects. Collected most frequently.
  • Generation 1 — survived one collection cycle.
  • Generation 2 — long-lived objects. Collected least frequently.

The idea: most objects die young. By checking new objects more often, we save time.

import gc

print(gc.get_count())       # (num_gen0, num_gen1, num_gen2)
print(gc.get_threshold())   # (700, 10, 10) — default thresholds

gc.collect()  # manually trigger a full collection

The GC runs automatically when the number of allocations minus deallocations in gen 0 exceeds the threshold (default 700).

__del__ Destructor

We can define __del__ to run cleanup code when an object is about to be destroyed. But it’s rarely used and can cause issues with the garbage collector.

class TempFile:
    def __init__(self, name):
        self.name = name
        print(f"Created {name}")

    def __del__(self):
        print(f"Deleting {self.name}")

f = TempFile("data.tmp")
del f  # Deleting data.tmp

Prefer context managers (with statement) over __del__ for cleanup — they’re more predictable.

Weak References

Sometimes we want to reference an object without preventing it from being garbage collected. That’s what weakref is for.

import weakref

class BigData:
    pass

obj = BigData()
weak = weakref.ref(obj)

print(weak())   # <__main__.BigData object ...>
del obj
print(weak())   # None — object was collected

In simple language, Python uses reference counting as its main memory strategy — when nothing points to an object anymore, it’s immediately freed. For tricky cases like circular references, the generational garbage collector steps in and cleans up periodically.