Memory & GC
Reference counting, the garbage collector, copy, and deepcopy.
7 questions
JuniorTheoryVery commonHow does reference counting reclaim memory?
How does reference counting reclaim memory?
Every object keeps a count of references; each binding increments it, deletion or rebinding decrements it. When the count hits zero the object is freed immediately — CPython's primary reclamation.
Common mistakes
- ✗Thinking memory is reclaimed only by the periodic GC, not by reference counting
- ✗Forgetting that
sys.getrefcount()reports one extra reference for its own argument - ✗Assuming reference counting can free reference cycles on its own
Follow-up questions
- →Why can't reference counting alone reclaim a reference cycle?
- →What thread-safety cost does reference counting impose via the GIL?
JuniorTheoryCommonWhat is the difference between copy() and deepcopy()?
What is the difference between copy() and deepcopy()?
copy.copy() makes a new outer object but copies only references to nested ones, so mutating a nested item shows in both. copy.deepcopy() recursively copies everything for independence.
Common mistakes
- ✗Assuming
copy()deep-copies nested lists or dicts so mutations stay isolated - ✗Forgetting that assignment
b = acopies nothing and just binds another name - ✗Believing
deepcopy()is always safe — it can recurse forever without proper handling of cycles
Follow-up questions
- →How does
deepcopy()handle an object that contains a reference cycle? - →How can
__copy__and__deepcopy__customize copying for your class?
MiddleTheoryOccasionalWhy does CPython need a cyclic GC on top of reference counting?
Why does CPython need a cyclic GC on top of reference counting?
Reference counting can't reclaim reference cycles — objects referring to each other keep one another's counts above zero even when unreachable. The optional cyclic GC (gc) frees such cycles.
Common mistakes
- ✗Believing the cyclic GC replaces reference counting instead of supplementing it
- ✗Assuming reference counting alone handles cycles correctly
- ✗Forgetting that only container objects that can form cycles are tracked by
gc
Follow-up questions
- →How does the cyclic GC tell an unreachable cycle from a still-referenced one?
- →How does
__del__on objects in a cycle interact with the cyclic GC?
MiddleTheoryOccasionalHow does the generational cyclic GC organize objects?
How does the generational cyclic GC organize objects?
It splits tracked containers into 3 generations. New ones start in gen 0, scanned most often; survivors are promoted to older generations scanned less often (thresholds 700, 10, 10).
Common mistakes
- ✗Thinking older generations are scanned more often rather than less
- ✗Assuming atomic immutables like
intare tracked by the cyclic collector - ✗Confusing the gen 0 threshold (allocations minus deallocations) with a time interval
Follow-up questions
- →What exactly does the gen 0 threshold of 700 count before triggering?
- →How do
gc.get_threshold()andgc.set_threshold()let you tune collection?
MiddleDebuggingOccasionalWhy does mutating the shallow copy change the original?
Why does mutating the shallow copy change the original?
copy.copy is shallow: the outer list is new, but the inner lists are shared references, so mutating shallow[0] also changes original → [[1, 2, 99], [3, 4]]. Fix: use copy.deepcopy(original) for fully independent nested data.
Common mistakes
- ✗Thinking
copy.copydeep-copies nested structures - ✗Believing a slice
[:]makes inner lists independent - ✗Not reaching for
copy.deepcopyfor nested data
Follow-up questions
- →When is a shallow copy actually the right choice over a deep copy?
- →How does
copy.deepcopyhandle a structure that contains a reference cycle?
SeniorTheoryRareCan you disable reference counting or the cyclic GC?
Can you disable reference counting or the cyclic GC?
Reference counting is fundamental and can't be disabled. The cyclic GC is optional: gc.disable() turns it off and gc.collect() runs it manually — avoiding pauses but risking leaked cycles.
Common mistakes
- ✗Thinking
gc.disable()stops all reclamation rather than just the cyclic collector - ✗Assuming reference counting can be turned off for performance
- ✗Forgetting that
gc.disable()risks leaking cycles untilgc.enable()orgc.collect()
Follow-up questions
- →When does disabling the
gcduring a batch load actually pay off? - →What does
gc.collect()return, and how cangc.freeze()help at fork time?
SeniorTheoryRareWhy might a long-running process not return freed memory to the OS?
Why might a long-running process not return freed memory to the OS?
CPython's pymalloc manages small objects (<512 B), reusing freed memory in pools instead of returning it to the OS. An arena is released only when fully empty, so growth isn't always a leak.
Common mistakes
- ✗Equating a non-shrinking RSS with a memory leak
- ✗Assuming every
delimmediately shrinks the process resident size - ✗Believing the GC, not pymalloc, controls return of memory to the OS
Follow-up questions
- →How do
arenasandpoolsin pymalloc map onto OS memory pages? - →Why are large allocations over 512 bytes routed straight to
mallocinstead?