Sample questions
Python Memory PerformanceDifficulty 1
x = [1, 2, 3]
y = x
del x
After this code runs, what happens to the list
[1, 2, 3]?
- aIt is deleted immediately, since
del x removes the list object itself - bIt stays alive, since
y still references it and the refcount only drops from 2 to 1✓ - cOnly the cycle-detecting collector can decide this; refcounting plays no role here
- d
del x raises an error, since deleting a name while another name still references the same object is forbidden
Explanation:del x removes the binding of the name x, decrementing the object's reference count by one. Since y still points to the same list, its refcount only drops from 2 to 1, so CPython keeps the object alive. The list would only be deallocated once its refcount reaches 0, i.e. when y also stops referencing it.
Python Memory PerformanceDifficulty 2
import sys
a = []
b = a
print(sys.getrefcount(a))
What does the printed number represent here?
- aEvery reference to the list, including the call's own temporary one (always created) — typically 3✓
- bOnly
a and b, since the call itself rarely counts as a reference — typically 2 - cAlways 1, no matter how many names alias the same object
- dIt raises
TypeError, since lists are unhashable
Explanation:sys.getrefcount(obj) reports the reference count of obj at the moment it is called, but passing obj as an argument itself creates one extra, temporary reference for the duration of the call. So with a and b both bound to the same list, the count typically comes out as 3 (a, b, and the call's own temporary reference), not 2.
Python Memory PerformanceDifficulty 2
def f():
x = [1, 2, 3]
return None
f()
After
f() returns, what happens to the list created inside it?
- aIt stays in memory until the program exits, since Python is not expected to free locals early
- bIt moves into a global object cache to be reused by later calls
- cIt stays reachable forever through
gc.get_objects() - dIts refcount drops to 0 once the frame is destroyed, so CPython frees it right away✓
Explanation:The only reference to the list is the local name x. Once f() returns, its stack frame (and the local variables it held) is destroyed, so x's reference to the list disappears. With refcount reaching 0, CPython's reference-counting collector frees the list immediately — there is no need to wait for a garbage collection cycle for this simple, non-circular case.
Python Memory PerformanceDifficulty 2
Two objects a and b end up referencing each other (a.other = b, b.other = a), and no other name in the program references either of them. Why doesn't reference counting alone free them?
- aReference counting eventually reaches 0 on its own after enough time passes
- bCircular references are forbidden and raise an error as soon as they are created
- cEach refcount stays above 0 forever, so only the
gc cycle collector can reclaim them✓ - dCPython silently converts such circular references into weak references to prevent leaks
Explanation:Even with no external names pointing at a or b, a still holds a reference to b and b still holds a reference to a, so neither refcount ever reaches 0 through pure reference counting. This is exactly the case the generational cycle-detecting garbage collector (gc module) exists for: it periodically looks for groups of objects that are only reachable from each other and reclaims them.
Python Memory PerformanceDifficulty 2
class Node:
def __init__(self):
self.other = None
a = Node()
b = Node()
a.other = b
b.other = a
del a
del b
After
del a; del b, are the two
Node objects freed immediately by reference counting alone?
- aNo — each keeps a refcount of 1 from the other, until the cycle collector runs✓
- bYes,
del typically forces immediate deallocation no matter how many references exist - cYes, Python detects self-referencing attributes and nulls them on
del - dNo, and they will never be freed for the rest of the process's lifetime
Explanation:Deleting the names a and b removes the external references, but a.other still points at b and b.other still points at a. Each object's refcount only drops from 2 to 1, never to 0, so plain reference counting cannot reclaim them. They remain garbage until the generational gc collector runs (automatically at some point, or via an explicit gc.collect()), at which point it recognizes the isolated cycle and frees both.
Python Memory PerformanceDifficulty 3
A batch job calls gc.disable() at startup to shave off collection overhead in a hot loop. Weeks later, memory usage grows steadily over long runs, and a memory profiler shows many Node-like objects that reference each other (parent/child back-references) piling up. What is the most likely explanation?
- a
gc.disable() only turns off generation 2; younger cycles are still collected automatically - bThe growth is unrelated to
gc.disable(); it must be a C extension leak instead - c
gc.disable() turns off the cycle collector entirely, so new cycles simply leak✓ - d
gc.disable() only suppresses gc debug statistics printing, not collection itself
Explanation:gc.disable() stops the automatic generational cycle collector from ever running. Reference counting still reclaims non-circular garbage as usual, but any object cycles (like parent/child back-references) will simply never be freed on their own once the collector is off — they sit in memory until either gc.enable() is called and a cycle eventually runs, or gc.collect() is invoked explicitly. For code that relies on cyclic structures, disabling gc for a performance win trades that win for a slow, steady memory leak.