del, references, and garbage collection

Deleting a name is not deleting an object; resource cleanup needs with

`del` in Python deletes names, not objects. The object survives as long as any reference to it remains. CPython uses reference counting as its primary memory management. Every `PyObject*` has an `ob_refcnt` field, incremented on reference acquisition and decremented on release. When the count reaches zero, the object is deallocated immediately. Cyclic garbage collection handles the remaining case: objects that reference each other but are no longer reachable from any root. Non-container objects like strings and integers are never tracked because they cannot form cycles. The `gc` module exposes collection control and debugging. `__del__` finalizers are unreliable for deterministic cleanup. Context managers via `with` are the correct pattern for resource management. <a href="/memory-tuples-lists">See how reference counting affects tuple and list memory</a>. <a href="/language-identity-equality">Understand how aliasing creates additional references</a>.

Understand.
Visualize.
Master.

Python in Depth

An interactive engineering reference for Python internals

Quick note

Cleanup belongs in `with`, not `__del__`.

:)
TABLE OF CONTENTS
2.8del, references, and garbage collection

Deleting a name is not deleting an object; resource cleanup needs with

You wrote del payload expecting the object to vanish instantly. The object is still there — alias still points to it. del removes a name, not an object.

Think of del like returning a library book to the returns slot. You have removed your association with the book, but the book still exists on the cart until the librarian (the interpreter) processes it. If someone else still has the same book checked out (another reference), it never even reaches the cart. The object lives as long as any reference points to it.

Core answer

Use del to remove references from names, attributes, items, or slices. Use context managers or explicit cleanup for external resources. Do not use __del__ as your normal correctness mechanism.

# [CURRENT - 3.10-3.14] Works on Python 3.x
payload = {"items": [1, 2, 3]}
alias = payload
del payload
print(alias["items"]) # object is still alive through alias
Mechanism and GC

The language-level rule is reachability: an object may be reclaimed after it becomes unreachable. CPython adds a concrete strategy on top of that:

  • immediate reclamation for many objects via reference counting
  • periodic cycle detection via the cyclic garbage collector
# [CURRENT - 3.10-3.14] Works on Python 3.x
import gc
print(gc.get_threshold())
print(gc.get_count())

Those thresholds and counters are CPython runtime tuning details rather than language guarantees. They exist because pure reference counting cannot reclaim unreachable cycles.

# [CURRENT - 3.10-3.14] Works on Python 3.x
a = []
b = [a]
a.append(b)
del a, b
# The cycle is unreachable, but reference counting alone cannot reclaim it.

On interpreters such as PyPy, object reclamation timing can differ materially because the implementation is not centered on CPython-style immediate reference-count drops.

CPython internals

CPython uses two complementary memory management mechanisms, both implemented in Objects/object.c and Modules/gcmodule.c.

Reference counting. Every PyObject in CPython starts with a Py_REFCNT field. Py_INCREF() increments it; Py_DECREF() decrements it and calls _Py_Dealloc() when it reaches zero. This is why del x on a local variable typically reclaims the object instantly: the DELETE_FAST opcode removes the fast-local binding and decrements the reference count right away. The reference count for a local variable lives in the function's fast-locals array; removing the binding decrements the count immediately.

Cyclic garbage collector (Modules/gcmodule.c, Python 3.12). The GC runs periodically to find and collect unreachable object cycles that reference counting alone cannot handle. It uses a generational approach with three generations:

  • generation 0: young objects, collected most frequently
  • generation 1: survivors of one collection
  • generation 2: long-lived objects, collected infrequently

The collection thresholds are tunable via gc.get_threshold() and default to (700, 10, 10) on CPython 3.12. That means: collect gen 0 every 700 allocations minus deallocations; collect gen 1 every 10 gen 0 collections; collect gen 2 every 10 gen 1 collections.

The GC algorithm tracks container objects (those that can participate in cycles: dict, list, set, tuple, custom class instances, etc.). Non-container objects such as strings, integers, and tuples containing no container objects are never tracked by the GC because they cannot own references to other objects that would form cycles.

During collection, the GC performs a reference-count subtraction phase to identify truly unreachable objects in a cycle, then calls the finalizer (if any) and reclaims the memory. Objects with __del__ methods still complicate cleanup because finalizers can observe partially torn-down object graphs, run at inconvenient times, or resurrect objects. Modern Python can collect many cycles with finalizers; gc.garbage is mostly for debug retention or extension types with legacy finalization hooks.

Use weakref.finalize instead of del for cleanup callbacks. The weakref.finalize object is a first-class handle that can be queried with .alive, detached with .detach(), and does not resurrect the object or create the same cyclical headaches that raw del methods do. See the weakref.finalize docs.

Finalization and weak references

__del__ is a finalizer hook, not a destructor you control directly. Finalization timing can vary, interpreter shutdown can complicate it, and cycles plus finalizers are historically tricky.

weakref.finalize is usually the safer observation mechanism when you need a callback attached to object lifetime without keeping the object alive.

# [CURRENT - 3.10-3.14] Works on Python 3.x
import gc
import weakref
class T:
pass
obj = T()
fin = weakref.finalize(obj, lambda: print("finalized"))
print(fin.alive)
del obj
gc.collect()
print(fin.alive)
Version context

del, context managers, gc, and weakref.finalize are stable Python 3 facilities. Current project guidance targets Python 3.10-3.14. Python 3.9 and below are End-of-Life.

The reachability rule is language-level. Immediate destruction on last-reference drop is a CPython-specific operational characteristic, not something to build correctness on across implementations.

Version-sensitive notes:

  • the default GC thresholds (700, 10, 10) are CPython 3.12 defaults and may change
  • Py_REF_DEBUG builds (debug mode) track reference counts differently
  • free-threaded CPython (PEP 703, Python 3.13+) uses biased reference counting and deferred reclamation for thread safety, meaning reference-count drops may not be immediate even on CPython
Edge cases and gotchas

If cleanup affects correctness, you need deterministic cleanup:

  • with for files, locks, transactions, and sockets
  • try/finally for narrow manual lifetimes
  • explicit close() or shutdown() APIs when the lifecycle is part of your domain
# [CURRENT - 3.10-3.14] Works on Python 3.x
from io import StringIO
def process(line):
print(line.strip().upper())
with StringIO("start\nstop\n") as fp:
for line in fp:
process(line)

Using del to "free memory now" is often cargo cult. It only helps when a reference would otherwise stay alive longer than necessary in the current scope.

Garbage collection is a memory-management mechanism, not a reliable resource-management policy. If release timing matters, make it explicit.

Production usage

Use del sparingly for large temporary objects inside long-running scopes when that genuinely reduces retained memory pressure.

# [CURRENT - 3.10-3.14] Works on Python 3.x
from pathlib import Path
def summarize(path):
raw = Path(path).read_bytes()
header = raw[:128]
del raw
return header.hex()

For finalization without ownership, prefer weakref.finalize. For handler/file cleanup patterns, pair this with the logging guidance in .

Further depth
  • Language reference: the del statement
  • gc module
  • weakref.finalize
  • Data model: object finalization
  • CPython source: Modules/gcmodule.c
  • CPython source: Objects/object.c
  • weakref.finalize docs
  • gc module docs
BOARD NOTESContext
WHY NO BENCHMARK?

This topic is better taught with structure, semantics, and cross-references than with a synthetic chart.

Cleanup belongs in `with`, not `__del__`.

RELATED GUIDES
NEXT CHECKS
Contribute