← Back to Python

Memory Management — GC, Ref Counting

PythonSeniorpython

The Question

How does Python manage memory with garbage collection and reference counting?

What a Strong Answer Covers

  • ref counting is primary
  • "GC handles cycles
  • "generational (3 gens)

Senior-Level Answer

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).

What Separates a 2/3 from a 3/3

2/3 — Passing but Incomplete

Correctly explains reference counting with ob_refcnt, identifies cycles as its limitation, and explains that the gc module handles cyclic collection.

3/3 — Strong Answer

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.

Common Mistakes

  • Saying Python uses 'garbage collection' without mentioning reference counting as the primary mechanism
  • Not knowing that cyclic GC is generational — treating it as a simple mark-and-sweep
  • Claiming gc.disable() disables all memory management — reference counting still runs
  • Forgetting that simple types like int and str are not tracked by the cyclic GC

Follow-Up Questions

  • Why doesn't reference counting alone handle this cycle: a = []; a.append(a)? — a's list contains a reference to itself. When del a runs, the list's refcount goes to 1 (self-reference), not 0 — never deallocated by refcounting alone.
  • What is a weak reference and when would you use it to break a cycle? — weakref.ref() creates a reference that doesn't increment refcount. Common in caches (parent->child strong, child->parent weak) and observer patterns.
  • How does the generational hypothesis justify Python's three-generation GC? — Most objects die young. Checking generation 0 frequently and generation 2 rarely amortizes the GC cost — most garbage is caught cheaply in gen 0.
  • What changed in Python 3.4 regarding __del__ and cycles? — PEP 442 — objects with __del__ in a cycle can now be finalized safely. Previously they were put in gc.garbage and never freed.

Related Questions

  • GIL — What It Is and What It Protects
  • ThreadPoolExecutor & ProcessPoolExecutor
  • Decorators — Under the Hood
  • Generators & yield
  • Context Managers

Can You Explain This Cold?

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
GrindQuestionsAITechnical interview assessment
TermsPrivacyAbout