The Garbage Collector and Cyclic References
Learn how the gc module handles reference cycles.
The Garbage Collector and Cyclic References is a free Python Academy lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Python Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Why Reference Counting Is Not Enough
Reference counting cannot collect objects involved in reference cycles — each holds a reference to the other, so neither count drops to 0.
# Reference cycle: a → b → a
a = {}
b = {"other": a}
a["other"] = b
del a, b
# Both objects are unreachable but refcount > 0
# Only the cyclic GC can collect themThe gc Module
gc is the cyclic garbage collector. It periodically scans tracked objects for unreachable cycles and collects them.
import gc
gc.collect() # trigger immediately
print(gc.garbage) # objects with __del__ in cycles (cannot auto-collect)Generational Collection
The GC uses three generations. New objects start in gen 0. Objects that survive a collection are promoted. Gen 0 is collected most frequently.
import gc
print(gc.get_count()) # (gen0, gen1, gen2) allocations since last collection
print(gc.get_threshold()) # (700, 10, 10) — thresholds for each generation
gc.set_threshold(1000, 15, 10) # tuneDetecting Cycles with gc.get_referents
Inspect which objects an object refers to — useful for understanding why something is not being collected.
import gc
a = []
b = [a]
a.append(b)
for ref in gc.get_referents(a):
print(ref) # shows bgc.get_objects()
gc.get_objects() returns all objects currently tracked by the GC. Useful for finding memory leaks.
import gc
before = len(gc.get_objects())
create_lots_of_objects()
after = len(gc.get_objects())
print(f"Leaked: {after - before} objects")Avoiding Cycles
Design data structures to avoid cycles: use weakrefs for back-references, use IDs instead of direct references, or break cycles explicitly before objects go out of scope.
import weakref
class Node:
def __init__(self, parent):
# weakref avoids cycle:
self.parent = weakref.ref(parent)
class Tree:
def __init__(self):
self.child = Node(self) # no hard cycle__del__ and Cycles
Objects with __del__ involved in a cycle cannot be auto-collected. They are placed in gc.garbage instead.
import gc
class Leaky:
def __del__(self): pass
a = Leaky()
b = Leaky()
a.other = b
b.other = a
del a, b
gc.collect()
print(gc.garbage) # [Leaky, Leaky] — cannot collectgc.freeze() — Python 3.7+
gc.freeze() freezes currently-tracked objects so they are never collected. Useful in long-running processes after startup to reduce GC pauses.
import gc
# After all globals and imports are in place:
gc.freeze() # no GC scanning for these objects ever again
# Useful for: gunicorn pre-fork, background serversDisabling and Re-enabling GC
Disable the cyclic GC in tight loops where you know no cycles exist, then re-enable and force a collection afterwards.
import gc
gc.disable()
try:
result = tight_loop_no_cycles()
finally:
gc.enable()
gc.collect()isenabled and isfinalized
Check GC state with gc.isenabled() and detect if an object is being finalized with gc.is_finalized(obj).
import gc
print(gc.isenabled()) # True by default
obj = object()
print(gc.is_finalized(obj)) # False (still alive)Memory Leak Patterns
Common Python memory leak causes: global caches that grow without bounds, event listeners not detached, cyclic references with __del__, and C extensions leaking references.
# Typical leaks:
global_cache = {} # grows forever — use lru_cache with maxsize
class EventBus:
listeners = []
# Listeners hold references to objects:
# @classmethod
# def on(cls, fn): cls.listeners.append(fn)
# Must call off() to detachQuick Check
What happens to objects with __del__ that are involved in a reference cycle?
Recap
The cyclic GC handles reference cycles that refcounting cannot. It uses three generations and promotes survivors. Avoid cycles with weakrefs. Objects with __del__ in cycles land in gc.garbage. Use gc.freeze() after startup to reduce GC pauses in long-running servers.
Frequently asked questions
Is the “The Garbage Collector and Cyclic References” lesson free?
Yes — the full text of “The Garbage Collector and Cyclic References” is free to read here on the web, and the Python Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Python Academy course, upgrade to CoddyKit PRO.
What will I learn in “The Garbage Collector and Cyclic References”?
Learn how the gc module handles reference cycles. You practise Python Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Python Academy?
No prior experience is required. Python Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “The Garbage Collector and Cyclic References” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Python Academy lesson?
Yes. Every Python Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- CPython Reference Counting
- The Garbage Collector and Cyclic References
- Profiling with cProfile and line_profiler
- Memory Profiling with tracemalloc