How does Python manage memory with garbage collection and reference counting?
CPython uses two complementary mechanisms for memory management: **reference counting** as the primary strategy and a **cyclic garbage collector** as a fallback for cyclic references that reference counting cannot handle.
**Reference Counting**
Every Python object has a reference count stored in its header (`ob_refcnt`). When a name is bound to an object, the count increments. When the binding is removed (variable goes out of scope, `del`, reassignment, collection modified), the count decrements. When the count reaches zero, the object is immediately deallocated — its `__del__` finalizer is called (if defined) and memory is returned to Python's memory allocator.
Advantages: deterministic destruction (objects are freed immediately when no longer reachable), simple implementation, low overhead for non-cyclic object graphs.
Disadvantage: **cyclic references cannot be freed by reference counting alone.** Two objects pointing to each other maintain a count of at least 1 even when no external references exist.
**Cyclic Garbage Collector (`gc` module)**
The `gc` module implements a generational collector that runs periodically to detect and break reference cycles. Objects are tracked in three generations (0, 1, 2). New objects start in generation 0. Objects that survive a collection are promoted to the next generation. Collection frequency decreases for higher generations (heuristic: older objects are less likely to be garbage).
The collector uses a mark-and-sweep variant: it traverses all tracked objects, decrements a copy of their reference counts for internal links (without affecting actual counts), and any object whose copy reaches zero has no external references — it is garbage. Cycles are then broken by clearing internal references before deallocation.
The GC only tracks **container objects** (lists, dicts, class instances, etc.) — not simple objects like integers or strings, which cannot participate in cycles.
**The GIL's role:** The Global Interpreter Lock serializes access to reference counts in CPython, preventing data races on `ob_refcnt` in multi-threaded code. This means reference count operations are thread-safe without per-object locking, but it also means only one thread runs Python bytecode at a time.
**Practical implications:** `gc.disable()` turns off the cyclic collector but not reference counting — useful in performance-critical, cycle-free code. `gc.collect()` triggers a manual collection. `weakref` breaks cycles intentionally by not incrementing reference counts. `__del__` finalizers on objects in cycles historically complicated collection (Python 3.4+ resolved most of this with PEP 442).
Correctly explains reference counting with ob_refcnt, identifies cycles as its limitation, and explains that the gc module handles cyclic collection.
Covers ref counting mechanics, cyclic collector generations, the GIL's role in thread-safe ref counting, weak references, and practical implications like gc.disable() and __del__ with cycles.
Reading the answer is step one. Explaining it unprompted — under interview pressure — is what actually matters. Get AI-graded feedback on your answer with follow-up probes on your weak points.
Get Graded — Free Assessment