Explanation
What just happened
CPython counts the references to every object. del a removed the last one, so a was freed right inside del, before the next line ran.
b and c pointed at each other. After del b, c, no code could reach them, but each still held the other, so neither count reached 0. They stayed in memory until gc.collect() ran the cycle collector, which spotted the unreachable pair and freed both.
Why there are two mechanisms
Reference counting is immediate and cheap, but it can’t see cycles. The cycle collector can, so it runs from time to time to catch them. Most objects never need it.
Watch out
gc.disable() turns off only the cycle collector. Cycles then live until the process ends, however much you allocate; everything else is still freed by reference counting.
Common questions
Does del free memory in Python?
Only if it removes the last reference. When an object's reference count drops to 0, CPython frees it right away, inside the del.
Why isn't my object freed after del?
Something else still refers to it, often another object it points to in turn. A reference cycle keeps every count above 0, so only the cycle collector can free it.
What does gc.collect() do?
It runs the cycle collector now. It finds groups of objects that only refer to each other and frees them; objects freed by reference counting never needed it.
Verified: this timeline matches a real program, checked in CI on Python 3.14, 3.15 and free-threaded 3.15t. How we verify. Unclear or wrong? Tell us