Skip to content

Exercises - Reference counting

Exercise 1🟢 Beginner Predict the reference count at each step before running the code:

import sys
a = [1, 2, 3]
print(sys.getrefcount(a)) # ?
b = a
print(sys.getrefcount(a)) # ?
c = a
print(sys.getrefcount(a)) # ?
del b
print(sys.getrefcount(a)) # ?
del c
print(sys.getrefcount(a)) # ?

Exercise 2🟢 Beginner Predict the reference count at each step. Think carefully about what happens when a variable is reassigned:

import sys
a = [1, 2, 3]
b = a
print(sys.getrefcount(a)) # ?
b = [4, 5, 6] # b is reassigned to a new object
print(sys.getrefcount(a)) # ?
print(sys.getrefcount(b)) # ?

Exercise 3🟡 Intermediate Predict the reference count at each step. Think carefully about what happens when an object is stored inside a list:

import sys
a = [1, 2, 3]
print(sys.getrefcount(a)) # ?
container = [a, a, a] # a is stored three times
print(sys.getrefcount(a)) # ?
container.pop() # remove one reference from container
print(sys.getrefcount(a)) # ?
del container # remove all remaining references
print(sys.getrefcount(a)) # ?

Exercise 4🟡 Intermediate Predict the reference count at each step. Think about how dict values affect reference counts:

import sys
a = {"name": "Alice"}
print(sys.getrefcount(a)) # ?
d = {"user": a} # a stored as dict value
print(sys.getrefcount(a)) # ?
d["admin"] = a # a stored again under different key
print(sys.getrefcount(a)) # ?
del d # entire dict removed
print(sys.getrefcount(a)) # ?

Exercise 5🟡 Intermediate Predict the reference count at each step. Think about what happens when a function receives an argument:

import sys
def inspect(obj):
print(sys.getrefcount(obj)) # ? — what is the count inside the function?
a = [1, 2, 3]
print(sys.getrefcount(a)) # ? — before the call
inspect(a) # what happens to refcount inside?
print(sys.getrefcount(a)) # ? — after the call, back to what?

Exercise 6🟡 Intermediate Predict what happens to the reference counts after the del statements. Draw a diagram showing why neither object is freed:

import sys
a = {}
b = {}
a["ref"] = b
b["ref"] = a
print(sys.getrefcount(a)) # ?
print(sys.getrefcount(b)) # ?
del a
del b
# Are the objects freed? Why or why not?
# What tool does Python use to handle this situation?

Exercise 7🟡 Intermediate This code creates a circular reference inside a list. Predict the reference counts and explain why the objects are not freed after del:

import sys
a = [1, 2, 3]
b = [4, 5, 6]
a.append(b) # a holds a reference to b
b.append(a) # b holds a reference to a
print(sys.getrefcount(a)) # ?
print(sys.getrefcount(b)) # ?
del a
del b
# Draw the diagram showing the state of the heap after del
# Why is the cyclic garbage collector needed here?

Exercise 8🔴 Advanced This code creates a three-way circular reference. 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 cycle
print(sys.getrefcount(a)) # ?
print(sys.getrefcount(b)) # ?
print(sys.getrefcount(c)) # ?
del a
del b
del c
# Draw the heap diagram showing:
# 1. the three objects and their internal references
# 2. why all three refcounts remain at 1
# 3. why none of them are freed by reference counting alone

Exercise 9🔴 Advanced Identify the memory issue in this code and explain why it is a problem:

class Node:
def __init__(self, value):
self.value = value
self.next = None
# Building a linked list
n1 = Node(1)
n2 = Node(2)
n3 = Node(3)
n1.next = n2
n2.next = n3
n3.next = n1 # ← what is wrong here?
del n1
del n2
del n3
# Questions:
# 1. What are the reference counts of n1, n2, n3 after del?
# 2. Are the objects freed?
# 3. How would you fix this?

Exercise 10🔴 Advanced This code builds a cache that unintentionally prevents objects from being freed. Identify the issue, explain it in terms of reference counting, and propose a fix using weakref:

import sys
cache = {}
class ExpensiveObject:
def __init__(self, name):
self.name = name
obj = ExpensiveObject("resource")
cache["key"] = obj # stored in cache
print(sys.getrefcount(obj)) # ?
del obj # we are done with obj
# Questions:
# 1. Is the object freed after del obj? Why or why not?
# 2. What is the reference count after del obj?
# 3. How would weakref solve this problem?
# hint: import weakref; cache["key"] = weakref.ref(obj)

Try predicting every reference count before running the code — the goal is to build an accurate mental model of how Python tracks object lifetimes, so memory issues become visible before they become bugs.