Skip to content

Exercises - Cyclic Garbage Collector

Exercise 1🟢 Beginner Predict the reference count at each step and explain why the objects are not freed after del:

import sys
class Node:
def __init__(self, value):
self.value = value
self.next = None
a = Node(1)
b = Node(2)
print(sys.getrefcount(a)) # ?
print(sys.getrefcount(b)) # ?
a.next = b
b.next = a
print(sys.getrefcount(a)) # ?
print(sys.getrefcount(b)) # ?
del a
del b
# Questions:
# 1. What is the refcount of each object after del?
# 2. Are the objects freed? Why or why not?
# 3. What will free them?

Exercise 2🟢 Beginner Identify whether each of the following creates a circular reference:

# Case 1
a = [1, 2, 3]
b = [4, 5, 6]
a.append(b)
b.append(a)
# Case 2
x = {"name": "Alice"}
y = {"friend": x}
# Case 3
p = []
p.append(p) # ← a list that contains itself
# Case 4
class A:
pass
obj1 = A()
obj2 = A()
obj1.ref = obj2
obj2.ref = obj1
# Questions for each case:
# 1. Is there a circular reference?
# 2. Draw the reference diagram
# 3. Will reference counting alone free these objects?

Exercise 3🟡 Intermediate Predict the reference counts at each step and draw the heap diagram after all del statements:

import sys
a = {}
b = {}
c = {}
a["next"] = b
b["next"] = c
c["next"] = a # closes the three-way cycle
print(sys.getrefcount(a)) # ?
print(sys.getrefcount(b)) # ?
print(sys.getrefcount(c)) # ?
del a
del b
del c
# Questions:
# 1. What is the refcount of each object after del?
# 2. Draw the heap diagram showing all three objects and their references
# 3. Why does none of them get freed?

Exercise 4🟢 Beginner Fill in the expected output and explain what each line does:

import gc
print(gc.isenabled()) # ?
print(gc.get_threshold()) # ?
print(gc.get_count()) # ?
gc.disable()
print(gc.isenabled()) # ?
gc.enable()
print(gc.isenabled()) # ?
freed = gc.collect()
print(freed) # ? — what does this number represent?

Exercise 5🟡 Intermediate Explain what happens to memory in this scenario — does the cyclic GC free the objects, and when?

import gc
gc.disable() # GC disabled
class Node:
def __init__(self, value):
self.value = value
self.next = None
def create_cycle():
a = Node(1)
b = Node(2)
a.next = b
b.next = a
return None # a and b go out of scope here
create_cycle() # cycle created and lost
# Questions:
# 1. Are the objects freed when create_cycle() returns?
# 2. What happens because gc.disable() was called?
# 3. How do you free them manually?
# 4. What is the risk of leaving GC disabled?

Exercise 6🟡 Intermediate Fill in the expected output and explain the generational thresholds:

import gc
print(gc.get_threshold()) # ?
# Questions:
# 1. When is Generation 0 collected?
# 2. When is Generation 1 collected?
# 3. When is Generation 2 collected?
# 4. Why are older generations collected less frequently?
# 5. What kind of objects end up in Generation 2?

Exercise 7🟡 Intermediate Predict which generation each object ends up in and explain why:

import gc
# Scenario 1 — short lived object
def short_lived():
x = [1, 2, 3] # created in gen 0
return None # immediately out of scope
# Scenario 2 — module level object
MY_CONFIG = { # where does this end up?
"host": "localhost",
"port": 8080,
}
# Scenario 3 — object that survives many collections
cache = {} # where does this end up over time?
for i in range(10000):
cache[i] = i * 2
# Questions:
# 1. Which generation does x in short_lived() end up in?
# 2. Which generation does MY_CONFIG end up in over time?
# 3. Which generation does cache end up in?
# 4. Why does the GC scan Generation 2 so rarely?

Exercise 8🟡 Intermediate Identify the memory issue in this code and explain how to fix it:

class EventSystem:
def __init__(self):
self.listeners = []
def add_listener(self, listener):
self.listeners.append(listener)
class Button:
def __init__(self, event_system):
self.event_system = event_system # Button holds EventSystem
event_system.add_listener(self) # EventSystem holds Button
btn = Button(EventSystem())
del btn
# Questions:
# 1. Is there a circular reference here?
# 2. Draw the reference diagram
# 3. Will the objects be freed by reference counting?
# 4. How would weakref solve this?

Exercise 9🔴 Advanced This code builds a tree structure where each node holds a reference to its parent and its children. Identify all circular references and explain the memory implications:

import sys
class TreeNode:
def __init__(self, value):
self.value = value
self.parent = None
self.children = []
def add_child(self, child):
child.parent = self # child holds reference to parent
self.children.append(child) # parent holds reference to child
root = TreeNode("root")
child = TreeNode("child")
root.add_child(child)
print(sys.getrefcount(root)) # ?
print(sys.getrefcount(child)) # ?
del root
del child
# Questions:
# 1. Draw the full reference diagram including parent and children references
# 2. What are the refcounts after del?
# 3. Are the objects freed?
# 4. How would you restructure this to avoid the cycle?
# hint: consider using weakref for the parent reference

Exercise 10🔴 Advanced This code implements a simple LRU cache that unintentionally creates cycles. Identify the cycles, explain the memory implications, and propose a fix:

import sys
import gc
class CacheNode:
def __init__(self, key, value):
self.key = key
self.value = value
self.prev = None # reference to previous node
self.next = None # reference to next node
class LRUCache:
def __init__(self):
self.cache = {}
self.head = CacheNode(None, None) # dummy head
self.tail = CacheNode(None, None) # dummy tail
self.head.next = self.tail # head → tail
self.tail.prev = self.head # tail → head ← cycle!
cache = LRUCache()
del cache
# Questions:
# 1. Identify all circular references in LRUCache
# 2. What are the refcounts of head and tail after del cache?
# 3. Draw the full reference diagram
# 4. Will reference counting alone free these objects?
# 5. How would you detect this with gc.collect()?

Try predicting every reference count and drawing every diagram before running the code — the goal is to build a precise mental model of how Python tracks object lifetimes so that memory leaks become visible before they become production bugs.