Skip to content

Exercises - Memory model

Exercise 1🟢 Beginner Predict the output of the following code before running it, then verify your answer.

a = [1, 2, 3]
b = a
c = [1, 2, 3]
print(a == b) # ?
print(a is b) # ?
print(a == c) # ?
print(a is c) # ?
b.append(4)
print(a) # ?
print(c) # ?

Exercise 2🟢 Beginner Predict the output of the following code before running it, then verify your answer.

a = 42
b = a
print(a is b) # ?
a += 1
print(a) # ?
print(b) # ?
print(a is b) # ?

Exercise 3🟡 Intermediate Predict the output of the following code before running it. Pay close attention to what is returns for integers inside and outside the CPython cache range.

a = 256
b = 256
print(a is b) # ?
a = 257
b = 257
print(a is b) # ?
a = -5
b = -5
print(a is b) # ?
a = -6
b = -6
print(a is b) # ?

Exercise 4🟢 Beginner Predict the output of the following code before running it.

import copy
a = [1, 2, 3]
b = a.copy()
a.append(4)
print(a) # ?
print(b) # ?
print(a is b) # ?

Exercise 5🟡 Intermediate Predict the output of the following code before running it. Think carefully about what shallow copy means for nested structures.

import copy
original = [[1, 2], [3, 4]]
shallow = original.copy()
deep = copy.deepcopy(original)
original[0].append(99)
original.append([5, 6])
print(original) # ?
print(shallow) # ?
print(deep) # ?

Exercise 6🟡 Intermediate Fix this function so that modifying the returned list does not affect the original:

# ❌ broken — modifying result affects original
def get_items(cart):
return cart
my_cart = ["apple", "banana", "cherry"]
result = get_items(my_cart)
result.append("date")
print(my_cart) # should be ["apple", "banana", "cherry"]
# but currently shows ["apple", "banana", "cherry", "date"]

Exercise 7🔴 Advanced Predict the output of the following code, then explain why shallow behaves the way it does.

import copy
original = {"a": [1, 2, 3], "b": [4, 5, 6]}
shallow = original.copy()
deep = copy.deepcopy(original)
original["a"].append(99)
original["c"] = [7, 8, 9]
print(original) # ?
print(shallow) # ?
print(deep) # ?

Exercise 8🟢 Beginner Rewrite this mutating function so it returns a new list instead of modifying the original:

# ❌ mutating
def double_all(numbers):
for i in range(len(numbers)):
numbers[i] *= 2
return numbers
nums = [1, 2, 3, 4, 5]
result = double_all(nums)
# after fix:
# nums should still be [1, 2, 3, 4, 5]
# result should be [2, 4, 6, 8, 10]

Exercise 9🟢 Beginner Rewrite this mutating function so it returns a new dict instead of modifying the original:

# ❌ mutating
def activate_user(user):
user["active"] = True
return user
u = {"name": "Alice", "active": False}
result = activate_user(u)
# after fix:
# u should still be {"name": "Alice", "active": False}
# result should be {"name": "Alice", "active": True}

Exercise 10🟡 Intermediate Rewrite this mutating function that removes all negative numbers from a list, without modifying the original:

# ❌ mutating
def remove_negatives(numbers):
for n in numbers[:]:
if n < 0:
numbers.remove(n)
return numbers
nums = [3, -1, 4, -2, 5, -3, 6]
result = remove_negatives(nums)
# after fix:
# nums should still be [3, -1, 4, -2, 5, -3, 6]
# result should be [3, 4, 5, 6]

Exercise 11🟡 Intermediate Rewrite this mutating function that applies a discount to all items in a shopping cart, without modifying the original:

# ❌ mutating
def apply_discount(cart, discount):
for item in cart:
item["price"] *= (1 - discount)
return cart
cart = [
{"name": "apple", "price": 1.0},
{"name": "banana", "price": 0.5},
{"name": "cherry", "price": 3.0},
]
result = apply_discount(cart, 0.10)
# after fix:
# cart should still have original prices
# result should have discounted prices:
# [{"name": "apple", "price": 0.9},
# {"name": "banana", "price": 0.45},
# {"name": "cherry", "price": 2.7}]

Exercise 12🔴 Advanced Rewrite this mutating function that updates a nested configuration dict, without modifying the original at any level:

# ❌ mutating
def update_config(config, section, key, value):
config[section][key] = value
return config
config = {
"server": {"host": "localhost", "port": 8080},
"database": {"host": "localhost", "port": 5432},
}
result = update_config(config, "server", "port", 3000)
# after fix:
# config["server"]["port"] should still be 8080
# result["server"]["port"] should be 3000
# config and result should be different objects
# config["database"] should be the same object in both
# (only the modified section needs a new object)

Try solving each exercise without using copy.deepcopy where possible — the goal is to build the habit of constructing new objects naturally rather than copying and mutating.