Who cleans up the heap
In lesson 4-1, heap objects live as long as anything points at them. Someone has to notice when nothing points at an object anymore and reclaim that memory.
In C, that someone is you, through malloc and free, and forgetting is a leak. In Python, Java, JavaScript, and Go, it is the garbage collector, usually abbreviated GC.
Python's main strategy is reference counting. Every object carries a count of how many references point at it. Assignment adds one, and deleting a name or a name going out of scope removes one. The moment the count hits zero, the object is freed immediately.
| Event | Effect on the count |
|---|---|
s = r | +1 |
del r | -1 |
| a function returns | -1 for each local name |
| count reaches 0 | the object is freed right there |
The immediacy is unusual. Many collectors free memory later, at a moment of their own choosing, so Python's behavior is observable with __del__, a method Python calls right when an object is being freed.
Watching a count reach zero
The order of the printed lines is the whole lesson.
class Resource: def __init__(self, name): self.name = name print("created", self.name) def __del__(self): print("freed", self.name) r = Resource("A") s = r del r print("still alive, s still points to it") del s print("after deleting the last reference")
Output
created A still alive, s still points to it freed A after deleting the last reference
The object survives del r because s still points at it, so the count went from 2 to 1 rather than to 0.
freed A prints before the final line, which is the proof of immediacy. The count hit zero at del s and reference counting freed the object at that instant, not at the end of the program.
A container holds a reference too
A list counts as a reference, so deleting the name is not enough to free the object.
class Resource: def __init__(self, name): self.name = name print("created", self.name) def __del__(self): print("freed", self.name) r = Resource("B") box = [r] del r print("still alive inside the list") box.clear() print("after the list let go")
Output
created B
still alive inside the list
freed B
after the list let gobox = [r] means the list holds a reference of its own, so del r leaves the count at 1 and the object lives on.
box.clear() drops the list's reference, the count hits zero, and freed B prints right there. This is the mechanism behind every cache-shaped memory leak: the collection is the surviving reference nobody remembered.
What this means for your code
Three consequences carry into real systems.
Leaks still happen in garbage-collected languages. The GC frees only unreachable objects, so a global list or cache that keeps growing keeps everything in it reachable forever. That is the slow-OOM pattern from lesson 4-2.
Reference counting has one blind spot, which is cycles. If A points to B and B points to A, their counts never reach zero even when nothing else can reach them. Python runs a second, occasional cycle collector to catch exactly this case.
Garbage collection costs CPU time. That is the trade, with C fast and manual while Python is safe and pays a tax.
| Language | Who frees memory | Failure mode |
|---|---|---|
| C | you, by hand | forgetting to free, or freeing twice |
| Python | reference counts plus a cycle collector | forgetting to stop referencing |
For most software the safety is worth the tax, and the interesting point is that the failure mode moved rather than disappeared.
Why a debug list defeats the collector
A Python service that stores every request it has ever seen in a module-level list will grow forever, and the garbage collector cannot help.
The GC frees only unreachable objects, and reachable means still accessible through some chain of references from live variables. Everything inside a live global list stays reachable, so the GC must keep it, since it cannot know the list will never be read again.
| Situation | GC verdict |
|---|---|
| object in a live global list | reachable, keep |
| object whose last name went out of scope | unreachable, free |
This is the classic leak in a garbage-collected language. The mistake is not forgetting to free, it is forgetting to stop referencing, and the fix is a bounded structure such as a fixed-size deque or a cache with eviction.