What Is Garbage Collection in Python? Explained Simply
Jul 08, 2026 4 Min Read 37 Views
(Last Updated)
Ever wondered why you rarely have to manually free memory in Python, unlike in C or C++? That’s garbage collection at work quietly running in the background, cleaning up objects you no longer need. Understanding how it actually works helps you write faster code and catch subtle memory issues before they become real problems.
Table of contents
- TL;DRSummary
- What Is Garbage Collection in Python?
- How Reference Counting Works
- The Problem: Reference Cycles
- The Generational Garbage Collector
- How the Algorithm Actually Finds Cycles
- Manual Garbage Collection
- Reference Counting vs Generational GC
- Avoiding Reference Cycles in Practice
- A Note on Python 3.12+: Immortal Objects
- Conclusion
- FAQs
- What is garbage collection in Python?
- How does reference counting work in Python?
- Why isn't reference counting enough on its own?
- What is the generational garbage collector?
- Can I manually run garbage collection in Python?
- How can I disable Python's garbage collector?
TL;DR Summary
- Python automatically manages memory through garbage collection, so developers rarely need to manually free unused objects.
- Two systems work together: reference counting handles most memory cleanup instantly, while the generational garbage collector detects and removes reference cycles.
- Understanding garbage collection helps prevent memory issues, optimize long-running applications, and debug unexpected memory usage.
Python’s garbage collection automatically frees memory by removing unused objects. Master Python internals and memory management with HCL GUVI’s Python Zero to Hero Course. Start your Python journey here
What Is Garbage Collection in Python?
Garbage collection is Python’s automatic system for reclaiming memory from objects that are no longer needed. It works through two mechanisms: reference counting, which tracks how many references point to each object, and a generational garbage collector, which catches reference cycles that reference counting alone can’t detect.
How Reference Counting Works
- Every Object Tracks Its Own References
In Python, every object even a simple integer has a reference count attached to it. This count increases whenever a new reference points to the object, and decreases whenever one is removed.
a = [1, 2, 3] b = a
Here, both a and b point to the same list. The assignment doesn’t copy any data it just adds a second reference to the same object in memory.
- When the Count Hits Zero
The moment an object’s reference count drops to zero, CPython immediately deallocates it. This happens through assignment, the del keyword, a variable going out of scope, or a container being deleted.
import sys
x = [1, 2, 3] print(sys.getrefcount(x)) # 2 one from x, one from getrefcount itself
y = x print(sys.getrefcount(x)) # 3 now x and y both reference it
y = None print(sys. getrefcount(x)) # back to 2
sys.getrefcount() always reports one more than you’d expect, because passing the object into the function temporarily creates an extra reference of its own.
- Why Reference Counting Is the Default
Reference counting is fast and predictable; an object is destroyed the instant it’s no longer needed, not at some unpredictable later point determined by a separate collector thread.
This immediacy is one of CPython’s defining design choices. It’s also part of why Python has historically needed the Global Interpreter Lock: updating a reference count safely across multiple threads requires some form of synchronization.
CPython, the reference implementation of Python, uses reference counting to destroy most objects as soon as their reference count reaches zero. However, objects involved in circular references cannot be reclaimed by reference counting alone. To handle these cases safely, CPython includes a separate cyclic garbage collector that periodically detects and removes unreachable groups of objects, helping prevent memory leaks in long-running applications.
The Problem: Reference Cycles
- When Objects Point at Each Other
Reference counting has one fundamental blind spot: it cannot detect reference cycles, where two or more objects reference each other directly or indirectly.
x = [1, 2, 3] y = [4, 5, 6]
x.append(y) y.append(x)
Now x contains y, and y contains x. Even if nothing else in your program references either list, their reference counts never drop to zero each is still holding the other alive.
- Why This Matters
Without a second mechanism, these objects would simply leak, sitting in memory forever, even though your code can no longer reach them.
Reference cycles aren’t rare edge cases either; they show up naturally in linked lists, trees with parent pointers, and any object graph where two pieces of data need to know about each other. This is exactly the gap Python’s generational garbage collector exists to close.
The Generational Garbage Collector
- Three Generations, Sorted by Age
Python’s cyclic garbage collector, accessible through the built-in gc module, organises every container object into one of three generations. Generation 0 holds newly created objects. Generation 1 holds objects that survived at least one collection round. Generation 2 holds long-lived objects that have survived multiple rounds.
This generational design is based on a simple observation: most objects die young. By checking generation 0 far more frequently than generation 2, Python avoids repeatedly re-scanning objects that have already proven they’re long-lived, which keeps collection overhead low.
- When Collection Actually Triggers
Each generation has its own allocation counter and threshold. Every time a new container object is created, Python checks whether generation 0’s counter has exceeded its threshold. If it has, a collection cycle runs.
import gc print(gc.get_threshold())
(700, 10, 10) exact default can vary slightly by Python version
You can inspect and adjust these thresholds directly with gc.set_threshold(), tuning collection frequency for workloads with unusual allocation patterns.
How the Algorithm Actually Finds Cycles
- The cyclic collector works by temporarily excluding references that originate within the tracked set of container objects.
- After this pass, any object whose remaining reference count is zero is unreachable from your actual code even though its raw reference count was never literally zero and gets collected.
- Only container objects lists, dictionaries, classes, and some tuples are tracked this way.
- Simple immutable objects like plain integers and strings are excluded, since they can’t participate in reference cycles in the first place.
- This selective tracking is what keeps the generational collector’s overhead manageable even in programs that allocate millions of small objects.
Manual Garbage Collection
- Forcing a Collection Cycle
Most of the time, you should let Python’s GC run on its own schedule. But you can manually trigger a full collection when needed:
import gc
a = [1, 2, 3] del a gc.collect()
This is occasionally useful right after a section of code that creates a large number of temporary cyclic structures, where you want memory reclaimed immediately rather than waiting for the next automatic trigger.
- Disabling the Collector
You can disable automatic cyclic collection entirely:
import gc gc.disable()
… performance-sensitive section …
gc.enable()
This is a real optimization technique in latency-sensitive applications, such as short-lived scripts, for instance, where the cyclic GC’s periodic pauses add overhead without ever paying off, since the whole process exits before cycles would matter anyway. Reference counting still runs regardless; disabling the GC only stops the cyclic-cycle scanner.
Reference Counting vs Generational GC
| Aspect | Reference Counting | Generational GC |
| Runs | Immediately, every reference change | Periodically, based on thresholds |
| Detects | Simple “no references left” cases | Reference cycles |
| Can be disabled | No fundamental to CPython | Yes via gc.disable() |
| Overhead | Small, constant, per-operation | Occasional, batched pauses |
| Handles | Every object | Only container objects |
Python’s garbage collection automatically frees memory by removing unused objects. Master Python internals and memory management with HCL GUVI’s Python Zero to Hero Course. Start your Python journey here
Avoiding Reference Cycles in Practice
- Use Weak References Where Possible
The weakref module lets you reference an object without increasing its reference count. If the object is destroyed elsewhere, the weak reference simply returns None instead of keeping it alive artificially.
import weakref
class Node: pass
a = Node() ref = weakref.ref(a) print(ref()) # <Node object>
del a print(ref()) # None
This is the standard fix for cycles that show up in graphs, trees with parent-pointers, or any structure where two objects naturally need to reference each other.
- Debugging Cycles With gc.garbage
Setting gc.set_debug(gc.DEBUG_SAVEALL) tells the collector to save every unreachable cyclic object it finds into the gc. garbage list instead of immediately freeing it, letting you inspect exactly what was being kept alive by a cycle.
A Note on Python 3.12+: Immortal Objects
- Python 3.12 introduced immortal objects, a special reference count value that tells CPython to skip reference counting entirely for certain objects, like None, True, and False, that live for the entire life of the process anyway.
- This optimization particularly benefits multiprocessing workloads that rely on copy-on-write memory sharing, since immortal objects never trigger the memory writes that normal reference count changes would otherwise cause.
Conclusion
Python’s garbage collection is really two systems working together: reference counting handles the vast majority of cleanup instantly and efficiently. In contrast, the generational garbage collector exists specifically to catch the reference cycles that counting alone can never see.
You rarely need to intervene, but understanding both mechanisms helps you reason about memory in long-running applications, recognize when gc.collect() or weakref might genuinely help, and avoid mistaking Python’s deliberate memory retention for an actual leak.
FAQs
1. What is garbage collection in Python?
Garbage collection is Python’s automatic memory management system. It identifies objects that are no longer being used and reclaims their memory, helping applications run efficiently without requiring manual memory deallocation.
2. How does reference counting work in Python?
Every Python object keeps track of how many references point to it. When the reference count drops to zero, CPython immediately removes the object from memory.
x = [1, 2, 3]
y = x
y = None
Once no variables reference the object, Python can safely free its memory.
3. Why isn’t reference counting enough on its own?
Reference counting cannot detect circular references. If two objects reference each other, their reference counts never reach zero, even when the rest of the program can no longer access them.
x = []
y = []
x.append(y)
y.append(x)
This creates a reference cycle that requires Python’s garbage collector to clean up.
4. What is the generational garbage collector?
Python’s generational garbage collector is designed to find and remove cyclic references. It organizes tracked objects into generations based on their age and periodically scans them for unreachable cycles.
The three generations are:
Generation 0: Newly created objects
Generation 1: Objects that survived earlier collections
Generation 2: Long-lived objects
5. Can I manually run garbage collection in Python?
Yes. The gc module allows you to trigger garbage collection manually.
import gc
gc.collect()
This can be useful after creating large temporary object graphs or when debugging memory-related issues.
6. How can I disable Python’s garbage collector?
You can disable and re-enable the cyclic garbage collector using the gc module:
import gc
gc.disable()
# Performance-sensitive code
gc.enable()
Reference counting continues to work even when cyclic garbage collection is disabled.



Did you enjoy this article?