Chapter 20: Common Pitfalls — Solutions

Worked solutions to the 130 Find the Bug exercises in Chapter 20: Common Pitfalls. Each one names the kind of bug, explains why the original misbehaved, and shows the corrected program.

Solutions stay folded until you open them — try the exercise first; the diagnosis is where the learning is.

Solutions 20.1.1–20.1.5

Solution 20.1.1 — Missing colon after if

Show the diagnosis and the fix

Bug type: Syntax

The if header is missing the trailing :, so Python cannot tell where the condition ends and the block begins. Adding the colon fixes the parse error.

temperature = 39
if temperature >= 38:
    print("Fever")

Back to Exercise 20.1.1 · sol_20_1_1.py

Solution 20.1.2 — Missing colon after for

Show the diagnosis and the fix

Bug type: Syntax

A for loop header must end with :. Without it the program will not run; adding the colon resolves it.

distances = [58, 108, 150]
for distance in distances:
    print(distance)

Back to Exercise 20.1.2 · sol_20_1_2.py

Solution 20.1.3 — Missing colon after while

Show the diagnosis and the fix

Bug type: Syntax

The while header needs a colon before its block. Adding : lets the countdown loop parse and run.

seconds = 3
while seconds > 0:
    print(seconds)
    seconds = seconds - 1

Back to Exercise 20.1.3 · sol_20_1_3.py

Solution 20.1.4 — Missing colon after def

Show the diagnosis and the fix

Bug type: Syntax

A function definition must end with : after the parameter list. Adding the colon makes the definition valid.

def rectangle_area(width, height):
    return width * height

print(rectangle_area(4, 5))  # 20

Back to Exercise 20.1.4 · sol_20_1_4.py

Solution 20.1.5 — Missing colon after elif

Show the diagnosis and the fix

Bug type: Syntax

Like if, an elif header requires a trailing :. Adding it lets the three-way branch parse.

score = 55
if score >= 60:
    print("Pass")
elif score >= 50:
    print("Borderline")
else:
    print("Fail")

Back to Exercise 20.1.5 · sol_20_1_5.py

Solutions 20.2.1–20.2.5

Solution 20.2.1 — Body not indented

Show the diagnosis and the fix

Bug type: Syntax

The print after the if must be indented to form the block. Indenting it by four spaces fixes the IndentationError.

member = "Alex"
if member == "Alex":
    print("Welcome back")

Back to Exercise 20.2.1 · sol_20_2_1.py

Solution 20.2.2 — Inconsistent indentation in a loop

Show the diagnosis and the fix

Bug type: Syntax

The final print is indented more deeply than the loop body, which Python reads as an unexpected indent. Aligning it at the outer level (after the loop) fixes it.

rainfall = [12, 8, 15]
total = 0
for amount in rainfall:
    total = total + amount
print(total)  # 35

Back to Exercise 20.2.2 · sol_20_2_2.py

Solution 20.2.3 — Function body not indented

Show the diagnosis and the fix

Bug type: Syntax

The return statement must be indented inside the function. Indenting it four spaces makes the definition valid.

def square_perimeter(side):
    return 4 * side

print(square_perimeter(6))  # 24

Back to Exercise 20.2.3 · sol_20_2_3.py

Solution 20.2.4 — Over-indented statement

Show the diagnosis and the fix

Bug type: Syntax

The second print is indented inconsistently relative to the if block, causing an IndentationError. Matching the block’s indentation (here, dedenting it to the top level) fixes it.

temperature = 100
if temperature >= 100:
    print("Boiling")
print("Done checking")

Back to Exercise 20.2.4 · sol_20_2_4.py

Solution 20.2.5 — Indentation mixing two blocks

Show the diagnosis and the fix

Bug type: Syntax

The closing print is indented two spaces, matching neither the loop body nor the top level, so Python raises an IndentationError. Dedenting it to the top level fixes it.

grades = [88, 91, 79]
for grade in grades:
    print(grade)
print("Report complete")

Back to Exercise 20.2.5 · sol_20_2_5.py

Solutions 20.3.1–20.3.5

Solution 20.3.1 — Assignment inside an if

Show the diagnosis and the fix

Bug type: Syntax

= assigns and cannot appear as a condition; comparing requires ==. Using == makes the test valid.

thermostat = 20
if thermostat == 20:
    print("Comfortable")

Back to Exercise 20.3.1 · sol_20_3_1.py

Solution 20.3.2 — Assignment when comparing a password length

Show the diagnosis and the fix

Bug type: Syntax

A condition must compare with ==, not assign with =. Switching to == fixes the error.

length = 8
if length == 8:
    print("Valid length")

Back to Exercise 20.3.2 · sol_20_3_2.py

Solution 20.3.3 — Equality used where assignment is meant

Show the diagnosis and the fix

Bug type: Runtime

balance == 100 compares instead of assigning, so balance is never created and the print raises NameError. Use a single = to assign.

balance = 100
print(balance)  # 100

Back to Exercise 20.3.3 · sol_20_3_3.py

Solution 20.3.4 — Assignment inside a while

Show the diagnosis and the fix

Bug type: Syntax

A while condition cannot use =; it needs a comparison. The intended loop continues while the count is below 50, so use <.

count = 5
while count < 50:
    count = count * 2
    print(count)

Back to Exercise 20.3.4 · sol_20_3_4.py

Solution 20.3.5 — Equality used for assignment

Show the diagnosis and the fix

Bug type: Runtime

speed == 60 does not create speed; the later print raises NameError. A single = assigns the value.

speed = 60
print("Speed:", speed)  # Speed: 60

Back to Exercise 20.3.5 · sol_20_3_5.py

Solutions 20.4.1–20.4.5

Solution 20.4.1 — Printing before assigning

Show the diagnosis and the fix

Bug type: Runtime

students is used on the first line but assigned on the second, raising NameError. Assign before using.

students = 30
print(students)

Back to Exercise 20.4.1 · sol_20_4_1.py

Solution 20.4.2 — Using a sum before it exists

Show the diagnosis and the fix

Bug type: Runtime

total is printed before it is computed, causing NameError. Move the assignment above the print.

first = 70
second = 85
total = first + second
print(total)

Back to Exercise 20.4.2 · sol_20_4_2.py

Solution 20.4.3 — Using a result before computing it

Show the diagnosis and the fix

Bug type: Runtime

area is referenced before it is assigned, raising NameError. Compute area first, then print it.

radius = 3
area = 3.14159 * radius * radius
print(area)

Back to Exercise 20.4.3 · sol_20_4_3.py

Solution 20.4.4 — Reading a counter that is set later

Show the diagnosis and the fix

Bug type: Runtime

laps is used before it is created, so the print raises NameError. Assign laps before printing it.

laps = 4
print("Laps:", laps)

Back to Exercise 20.4.4 · sol_20_4_4.py

Solution 20.4.5 — Using a price before defining it

Show the diagnosis and the fix

Bug type: Runtime

price is multiplied before it is assigned, raising NameError. Define price before the calculation.

quantity = 3
price = 2
print(quantity * price)

Back to Exercise 20.4.5 · sol_20_4_5.py

Solutions 20.5.1–20.5.5

Solution 20.5.1 — Misspelled variable in a print

Show the diagnosis and the fix

Bug type: Runtime

greting is a different name from greeting, so Python raises NameError. Use the correct spelling.

greeting = "Good morning"
print(greeting)

Back to Exercise 20.5.1 · sol_20_5_1.py

Solution 20.5.2 — Misspelled variable in a calculation

Show the diagnosis and the fix

Bug type: Runtime

sped was never defined; only speed exists, so the line raises NameError. Fix the spelling.

speed = 60
hours = 2
distance = speed * hours
print(distance)  # 120

Back to Exercise 20.5.2 · sol_20_5_2.py

Solution 20.5.3 — Inconsistent capitalization

Show the diagnosis and the fix

Bug type: Runtime

Python is case-sensitive: Temperature and temperature are different names, so the print raises NameError. Match the case used at assignment.

Temperature = 31
print(Temperature)

Back to Exercise 20.5.3 · sol_20_5_3.py

Solution 20.5.4 — Misspelled variable when updating

Show the diagnosis and the fix

Bug type: Runtime

bil is a misspelling of bill, so the calculation raises NameError. Use the correct name.

bill = 40
tip = 6
total = bill + tip
print(total)  # 46

Back to Exercise 20.5.4 · sol_20_5_4.py

Solution 20.5.5 — Misspelled list name

Show the diagnosis and the fix

Bug type: Runtime

score (singular) was never defined; the list is named scores, so sum(score) raises NameError. Pass the correct list name.

scores = [80, 90, 100]
average = sum(scores) / len(scores)
print(average)  # 90.0

Back to Exercise 20.5.5 · sol_20_5_5.py

Solutions 20.6.1–20.6.5

Solution 20.6.1 — Joining text and a number

Show the diagnosis and the fix

Bug type: Runtime

You cannot add a string to an integer; "Steps today: " + steps raises TypeError. Convert the number with str().

steps = 8000
print("Steps today: " + str(steps))

Back to Exercise 20.6.1 · sol_20_6_1.py

Solution 20.6.2 — Adding a number to a string label

Show the diagnosis and the fix

Bug type: Runtime

Mixing str and int with + raises TypeError. Convert liters to a string before concatenating.

liters = 2
print("Drink " + str(liters) + " liters")

Back to Exercise 20.6.2 · sol_20_6_2.py

Solution 20.6.3 — Concatenating a price into a message

Show the diagnosis and the fix

Bug type: Runtime

"...$" + price adds a string and an int, raising TypeError. Wrap price in str().

price = 15
message = "Ticket costs $" + str(price)
print(message)

Back to Exercise 20.6.3 · sol_20_6_3.py

Solution 20.6.4 — Combining a count with text

Show the diagnosis and the fix

Bug type: Runtime

books + " books..." adds an int to a string, raising TypeError. Convert books to a string first.

books = 12
print(str(books) + " books on the shelf")

Back to Exercise 20.6.4 · sol_20_6_4.py

Solution 20.6.5 — Treating a string as a number

Show the diagnosis and the fix

Bug type: Runtime/Logical

score holds the text "75", so score + 10 mixes a string and an int and raises TypeError. Convert score to an integer (or store it as one) before adding.

score = int("75")
print(score + 10)

Back to Exercise 20.6.5 · sol_20_6_5.py

Solutions 20.7.1–20.7.5

Solution 20.7.1 — Adding to raw input

Show the diagnosis and the fix

Bug type: Runtime

input() returns text, so year + 1 adds a string and an int, raising TypeError. Wrap the input in int().

year = int(input("Enter the year: "))
print(year + 1)

Back to Exercise 20.7.1 · sol_20_7_1.py

Solution 20.7.2 — Doubling a typed quantity

Show the diagnosis and the fix

Bug type: Logical

cookies is text, so cookies * 2 repeats the string (e.g. "1212") instead of doubling the number. Convert the input to int first.

cookies = int(input("How many cookies? "))
print(cookies * 2 == 24)  # for input 12

Back to Exercise 20.7.2 · sol_20_7_2.py

Solution 20.7.3 — Summing two typed numbers

Show the diagnosis and the fix

Bug type: Runtime

second is left as text, so first + second adds an int and a string, raising TypeError. Convert second with int() as well.

first = int(input("First price: "))
second = int(input("Second price: "))
print(first + second)

Back to Exercise 20.7.3 · sol_20_7_3.py

Solution 20.7.4 — Comparing typed age to a limit

Show the diagnosis and the fix

Bug type: Runtime

age is a string, so age >= 18 compares a string with an int and raises TypeError. Convert the input to int before comparing.

age = int(input("Your age: "))
if age >= 18:
    print("Adult")

Back to Exercise 20.7.4 · sol_20_7_4.py

Solution 20.7.5 — Averaging a typed temperature

Show the diagnosis and the fix

Bug type: Runtime

temperature is text, so temperature / 2 raises TypeError. Convert the input to a number first (float allows decimals).

temperature = float(input("Temperature: "))
print(temperature / 2)

Back to Exercise 20.7.5 · sol_20_7_5.py

Solutions 20.8.1–20.8.5

Solution 20.8.1 — Splitting students into teams

Show the diagnosis and the fix

Bug type: Logical

/ gives 7.5, but full teams need integer division. Use // to get 7.

students = 30
team_size = 4
print(students // team_size)  # 7

Back to Exercise 20.8.1 · sol_20_8_1.py

Solution 20.8.2 — Pages per chapter

Show the diagnosis and the fix

Bug type: Logical

52 / 5 is 10.4; the program wants whole pages. Use // for the integer quotient 10.

pages = 52
chapters = 5
print(pages // chapters)  # 10

Back to Exercise 20.8.2 · sol_20_8_2.py

Solution 20.8.3 — Counting full boxes

Show the diagnosis and the fix

Bug type: Logical

40 / 6 is 6.66..., but you can only fill whole boxes. // gives the correct 6.

eggs = 40
per_box = 6
full_boxes = eggs // per_box
print(full_boxes)  # 6

Back to Exercise 20.8.3 · sol_20_8_3.py

Solution 20.8.4 — Whole minutes from seconds

Show the diagnosis and the fix

Bug type: Logical

200 / 60 is 3.33...; whole minutes need //, giving 3.

seconds = 200
minutes = seconds // 60
print(minutes)  # 3

Back to Exercise 20.8.4 · sol_20_8_4.py

Solution 20.8.5 — Sharing marbles evenly

Show the diagnosis and the fix

Bug type: Logical

25 / 3 is 8.33...; each child gets a whole number of marbles. // yields 8.

marbles = 25
children = 3
print(marbles // children)  # 8

Back to Exercise 20.8.5 · sol_20_8_5.py

Solutions 20.9.1–20.9.5

Solution 20.9.1 — Area of a square

Show the diagnosis and the fix

Bug type: Logical

^ is bitwise XOR, not exponent, so 5 ^ 2 is 7, not 25. Use ** for powers.

side = 5
print(side ** 2)  # 25

Back to Exercise 20.9.1 · sol_20_9_1.py

Solution 20.9.2 — Cube of a number

Show the diagnosis and the fix

Bug type: Logical

4 ^ 3 computes XOR (7), not 4 cubed. Use **.

base = 4
print(base ** 3)  # 64

Back to Exercise 20.9.2 · sol_20_9_2.py

Solution 20.9.3 — Compound growth

Show the diagnosis and the fix

Bug type: Logical

2 ^ 10 is XOR (8), not 2 to the tenth. Use ** for exponentiation.

print(2 ** 10)  # 1024

Back to Exercise 20.9.3 · sol_20_9_3.py

Solution 20.9.4 — Volume of a cube

Show the diagnosis and the fix

Bug type: Logical

3 ^ 3 is XOR (0), not 3 cubed. Use ** to raise to a power.

edge = 3
volume = edge ** 3
print(volume)  # 27

Back to Exercise 20.9.4 · sol_20_9_4.py

Solution 20.9.5 — Energy term squared

Show the diagnosis and the fix

Bug type: Logical

6 ^ 2 is XOR (4), not 6 squared. Use **.

speed = 6
print(speed ** 2)  # 36

Back to Exercise 20.9.5 · sol_20_9_5.py

Solutions 20.10.1–20.10.5

Solution 20.10.1 — Setting a fourth temperature

Show the diagnosis and the fix

Bug type: Runtime

readings has indexes 0–2, so assigning to readings[3] raises IndexError; assignment cannot grow a list. Use append to add a fourth value.

readings = [20, 22, 21]
readings.append(23)
print(readings)

Back to Exercise 20.10.1 · sol_20_10_1.py

Solution 20.10.2 — Recording a new score

Show the diagnosis and the fix

Bug type: Runtime

scores[3] does not exist yet, so assigning to it raises IndexError. Use append to add the new score.

scores = [10, 15, 20]
scores.append(25)
print(scores)

Back to Exercise 20.10.2 · sol_20_10_2.py

Solution 20.10.3 — Filling in a weekly total

Show the diagnosis and the fix

Bug type: Runtime

Index 3 is past the end of a three-item list, so the assignment raises IndexError. Use append to extend the list.

weekly_sales = [100, 120, 90]
weekly_sales.append(110)
print(weekly_sales)

Back to Exercise 20.10.3 · sol_20_10_3.py

Solution 20.10.4 — Adding a fourth runner’s time

Show the diagnosis and the fix

Bug type: Runtime

len(lap_times) is 3, which is one past the last valid index, so the assignment raises IndexError. Use append.

lap_times = [45, 47, 44]
lap_times.append(46)
print(lap_times)

Back to Exercise 20.10.4 · sol_20_10_4.py

Solution 20.10.5 — Appending a measurement

Show the diagnosis and the fix

Bug type: Runtime

Assigning to ph_values[3] on a three-item list raises IndexError. Use append to add a new reading.

ph_values = [7.0, 6.8, 7.2]
ph_values.append(6.9)
print(ph_values)

Back to Exercise 20.10.5 · sol_20_10_5.py

Solutions 20.11.1–20.11.5

Solution 20.11.1 — Reading the last color

Show the diagnosis and the fix

Bug type: Runtime

Indexes run 0–2, so colors[3] is out of range and raises IndexError. The last item is at index 2.

colors = ["red", "green", "blue"]
print(colors[2])

Back to Exercise 20.11.1 · sol_20_11_1.py

Solution 20.11.2 — Printing each day’s high

Show the diagnosis and the fix

Bug type: Runtime

range(5) reaches index 4, but the list has only indexes 0–3, so the loop raises IndexError. Loop to len(highs) (or iterate the list directly).

highs = [28, 30, 29, 31]
for i in range(len(highs)):
    print(highs[i])

Back to Exercise 20.11.2 · sol_20_11_2.py

Solution 20.11.3 — Showing the third prize

Show the diagnosis and the fix

Bug type: Runtime

The list has only two items (indexes 0 and 1), so prizes[2] raises IndexError. To print a third prize, the list must contain one.

prizes = ["gold", "silver", "bronze"]
print(prizes[2])

Back to Exercise 20.11.3 · sol_20_11_3.py

Solution 20.11.4 — Last item by length

Show the diagnosis and the fix

Bug type: Runtime

len(students) is 3, one past the last index, so students[3] raises IndexError. The last index is len(students) - 1.

students = ["Mia", "Noah", "Liam"]
print(students[len(students) - 1])

Back to Exercise 20.11.4 · sol_20_11_4.py

Solution 20.11.5 — Looping one step too far

Show the diagnosis and the fix

Bug type: Runtime

index <= len(prices) lets index reach 3, which is out of range, so the loop raises IndexError. Stop with < instead of <=.

prices = [3, 5, 9]
index = 0
while index < len(prices):
    print(prices[index])
    index = index + 1

Back to Exercise 20.11.5 · sol_20_11_5.py

Solutions 20.12.1–20.12.5

Solution 20.12.1 — Building a shopping list

Show the diagnosis and the fix

Bug type: Runtime

Parentheses create a tuple, which has no append method, so the call raises AttributeError. Use square brackets to make a list.

groceries = ["milk", "bread", "eggs"]
groceries.append("butter")
print(groceries)

Back to Exercise 20.12.1 · sol_20_12_1.py

Solution 20.12.2 — Collecting daily steps

Show the diagnosis and the fix

Bug type: Runtime

steps is a tuple, so append raises AttributeError. Use brackets to create a list.

steps = [8000, 9500, 7000]
steps.append(10000)
print(steps)

Back to Exercise 20.12.2 · sol_20_12_2.py

Solution 20.12.3 — A list of temperatures

Show the diagnosis and the fix

Bug type: Runtime

Parentheses make a tuple, which is immutable, so temperatures[0] = 19 raises TypeError. Use brackets to make an editable list.

temperatures = [21, 22, 20]
temperatures[0] = 19
print(temperatures)

Back to Exercise 20.12.3 · sol_20_12_3.py

Solution 20.12.4 — Listing class names

Show the diagnosis and the fix

Bug type: Runtime

subjects is a tuple, so append raises AttributeError. Use square brackets for a list.

subjects = ["Math", "Science"]
subjects.append("History")
print(subjects)

Back to Exercise 20.12.4 · sol_20_12_4.py

Solution 20.12.5 — A list of scores to extend

Show the diagnosis and the fix

Bug type: Runtime

Parentheses create a tuple, which has no append, raising AttributeError. Use brackets to make a list.

scores = [88, 92]
scores.append(75)
print(scores)

Back to Exercise 20.12.5 · sol_20_12_5.py

Solutions 20.13.1–20.13.5

Solution 20.13.1 — Capitalizing a name

Show the diagnosis and the fix

Bug type: Runtime

Strings are immutable, so name[0] = "S" raises TypeError. Build a new string instead.

name = "sam"
name = "S" + name[1:]
print(name)

Back to Exercise 20.13.1 · sol_20_13_1.py

Solution 20.13.2 — Fixing a typo in a word

Show the diagnosis and the fix

Bug type: Runtime

You cannot assign to a single character of a string; word[1] = "e" raises TypeError. Rebuild the word from slices.

word = "hpllo"
word = word[0] + "e" + word[2:]
print(word)

Back to Exercise 20.13.2 · sol_20_13_2.py

Solution 20.13.3 — Replacing a digit in a code

Show the diagnosis and the fix

Bug type: Runtime

Strings are immutable, so code[0] = "9" raises TypeError. Construct a new string.

code = "12345"
code = "9" + code[1:]
print(code)

Back to Exercise 20.13.3 · sol_20_13_3.py

Solution 20.13.4 — Masking a letter

Show the diagnosis and the fix

Bug type: Runtime

Assigning to secret[3] fails because strings cannot be changed in place, raising TypeError. Rebuild the string from its slices.

secret = "open"
secret = secret[:3] + "*"
print(secret)

Back to Exercise 20.13.4 · sol_20_13_4.py

Solution 20.13.5 — Correcting a unit label

Show the diagnosis and the fix

Bug type: Runtime

unit[0] = "K" tries to mutate an immutable string, raising TypeError. Make a new string instead.

unit = "km"
unit = "K" + unit[1:]
print(unit)

Back to Exercise 20.13.5 · sol_20_13_5.py

Solutions 20.14.1–20.14.5

Solution 20.14.1 — Checking a student’s full name

Show the diagnosis and the fix

Bug type: Logical

The name built with join is a brand-new string object, so is (which tests identity) returns False even though the text matches. Compare values with ==.

enrolled_name = "Ada Lovelace"
typed_name = " ".join(["Ada", "Lovelace"])
if typed_name == enrolled_name:
    print("Name matches our records")
else:
    print("Name does not match")

Back to Exercise 20.14.1 · sol_20_14_1.py

Solution 20.14.2 — Matching a password phrase

Show the diagnosis and the fix

Bug type: Logical

The built string is a different object from the stored literal, so is returns False even though the text matches. Compare values with ==.

stored_phrase = "open sesame"
typed_phrase = " ".join(["open", "sesame"])
if typed_phrase == stored_phrase:
    print("Access granted")
else:
    print("Access denied")

Back to Exercise 20.14.2 · sol_20_14_2.py

Solution 20.14.3 — Confirming a recipe title

Show the diagnosis and the fix

Bug type: Logical

The title built by concatenation is a separate string object, so is returns False even when the text is identical. Use == to compare the values.

saved_title = "Banana Bread"
first = "Banana"
second = "Bread"
built_title = first + " " + second
if built_title == saved_title:
    print("Recipe title matches")
else:
    print("Recipe title differs")

Back to Exercise 20.14.3 · sol_20_14_3.py

Solution 20.14.4 — Comparing two shopping lists

Show the diagnosis and the fix

Bug type: Logical

Two lists with identical contents are still distinct objects, so is is always False here. Use == to compare contents.

cart_one = ["milk", "eggs", "bread"]
cart_two = ["milk", "eggs", "bread"]
if cart_one == cart_two:
    print("The carts hold the same items")
else:
    print("The carts differ")

Back to Exercise 20.14.4 · sol_20_14_4.py

Solution 20.14.5 — Verifying a reading list

Show the diagnosis and the fix

Bug type: Logical

Two lists with identical contents are still different objects, so is is always False here. Use == to compare contents.

planned = ["Physics", "Algebra", "Biology"]
read_so_far = ["Physics", "Algebra", "Biology"]
if read_so_far == planned:
    print("You finished the planned books!")
else:
    print("Some planned books remain")

Back to Exercise 20.14.5 · sol_20_14_5.py

Solutions 20.15.1–20.15.5

Solution 20.15.1 — Reading the current temperature

Show the diagnosis and the fix

Bug type: Logical

Writing current_temperature without parentheses prints the function object instead of calling it. Add () to invoke it.

def current_temperature():
    return 21.5

print("Temperature:", current_temperature())

Back to Exercise 20.15.1 · sol_20_15_1.py

Solution 20.15.2 — Counting words in a sentence

Show the diagnosis and the fix

Bug type: Logical

word_count alone is the function object; it was never called with the sentence. Call it as word_count(note).

def word_count(sentence):
    return len(sentence.split())

note = "the cat sat on the mat"
print("Words:", word_count(note))

Back to Exercise 20.15.2 · sol_20_15_2.py

Solution 20.15.3 — Rolling for a starting number

Show the diagnosis and the fix

Bug type: Logical

score = starting_score stores the function itself, not its result. Add () to call it.

def starting_score():
    return 100

score = starting_score()
print("You begin with", score, "points")

Back to Exercise 20.15.3 · sol_20_15_3.py

Solution 20.15.4 — Area of a circle

Show the diagnosis and the fix

Bug type: Runtime

circle_area / 2 tries to divide the function object by 2, raising TypeError. Call the function first, then divide its result.

import math

def circle_area(radius):
    return math.pi * radius ** 2

print("Area:", circle_area(4))
print("Half area:", circle_area(4) / 2)

Back to Exercise 20.15.4 · sol_20_15_4.py

Solution 20.15.5 — Greeting the next runner

Show the diagnosis and the fix

Bug type: Logical

message = greet_runner stores the function, so the print shows a function object. Call it with an argument.

def greet_runner(name):
    return "Good luck, " + name + "!"

message = greet_runner("Sam")
print(message)

Back to Exercise 20.15.5 · sol_20_15_5.py

Solutions 20.16.1–20.16.5

Solution 20.16.1 — Converting miles to kilometers

Show the diagnosis and the fix

Bug type: Logical

The function computes km but never returns it, so the call yields None. Add a return.

def miles_to_km(miles):
    km = miles * 1.60934
    return km

print("Kilometers:", miles_to_km(5))

Back to Exercise 20.16.1 · sol_20_16_1.py

Solution 20.16.2 — Averaging three test grades

Show the diagnosis and the fix

Bug type: Logical

The average is computed into average_value but never returned, so None is printed. Return the value.

def average(a, b, c):
    total = a + b + c
    average_value = total / 3
    return average_value

print("Average:", average(80, 90, 100))

Back to Exercise 20.16.2 · sol_20_16_2.py

Solution 20.16.3 — Doubling a recipe

Show the diagnosis and the fix

Bug type: Logical

doubled is computed but not returned, so flour becomes None. Add return doubled.

def double_amount(cups):
    doubled = cups * 2
    return doubled

flour = double_amount(2.5)
print("Use", flour, "cups of flour")

Back to Exercise 20.16.3 · sol_20_16_3.py

Solution 20.16.4 — Perimeter of a rectangle

Show the diagnosis and the fix

Bug type: Logical

The perimeter p is calculated but never returned, so the program prints None. Return p.

def perimeter(length, width):
    p = 2 * (length + width)
    return p

print("Perimeter:", perimeter(6, 4))

Back to Exercise 20.16.4 · sol_20_16_4.py

Solution 20.16.5 — Tax on a purchase

Show the diagnosis and the fix

Bug type: Logical

The tax is computed into tax but never returned, so the result is None. Add a return.

def tax_owed(price, rate):
    tax = price * rate / 100
    return tax

print("Tax:", tax_owed(200, 8))

Back to Exercise 20.16.5 · sol_20_16_5.py

Solutions 20.17.1–20.17.5

Solution 20.17.1 — Tallying rainfall

Show the diagnosis and the fix

Bug type: Runtime

Assigning to total_rain inside the function makes it local, so reading it on the right raises UnboundLocalError. Declare it global.

total_rain = 0.0

def add_rain(today):
    global total_rain
    total_rain = total_rain + today

add_rain(1.2)
print("Total rainfall:", total_rain)

Back to Exercise 20.17.1 · sol_20_17_1.py

Solution 20.17.2 — Keeping a running balance

Show the diagnosis and the fix

Bug type: Runtime

The function assigns to balance, so Python treats it as local and the read raises UnboundLocalError. Add global balance.

balance = 500

def withdraw(amount):
    global balance
    balance = balance - amount

withdraw(120)
print("Balance:", balance)

Back to Exercise 20.17.2 · sol_20_17_2.py

Solution 20.17.3 — Counting visitors

Show the diagnosis and the fix

Bug type: Runtime

Because visitors is assigned inside enter, Python marks it local and raises UnboundLocalError. Declare it global.

visitors = 0

def enter():
    global visitors
    visitors = visitors + 1

enter()
print("Visitors:", visitors)

Back to Exercise 20.17.3 · sol_20_17_3.py

Solution 20.17.4 — Accumulating distance

Show the diagnosis and the fix

Bug type: Runtime

Assigning distance_km inside drive makes it local, so the read raises UnboundLocalError. Add global distance_km.

distance_km = 0

def drive(leg):
    global distance_km
    distance_km = distance_km + leg

drive(45)
print("Distance:", distance_km)

Back to Exercise 20.17.4 · sol_20_17_4.py

Solution 20.17.5 — Building a points streak

Show the diagnosis and the fix

Bug type: Runtime

streak is assigned inside the function, so Python treats it as local and raises UnboundLocalError. Declare it global.

streak = 1

def double_streak():
    global streak
    streak = streak * 2

double_streak()
print("Streak:", streak)

Back to Exercise 20.17.5 · sol_20_17_5.py

Solutions 20.18.1–20.18.5

Solution 20.18.1 — Saving a shopping list

Show the diagnosis and the fix

Bug type: Logical

The file is opened but never closed, so the handle leaks and buffered data may not flush. Use a with block, which closes automatically.

groceries = "milk\neggs\nbread\n"
with open("groceries.txt", "w") as list_file:
    list_file.write(groceries)
print("Shopping list saved")

Back to Exercise 20.18.1 · sol_20_18_1.py

Solution 20.18.2 — Logging a temperature reading

Show the diagnosis and the fix

Bug type: Logical

The file handle is never closed. Wrap the write in a with block so the file is closed safely.

with open("temps.txt", "a") as log:
    log.write("21.5\n")

Back to Exercise 20.18.2 · sol_20_18_2.py

Solution 20.18.3 — Recording a high score

Show the diagnosis and the fix

Bug type: Logical

score_file is opened but never closed. Use with open(...) so the file closes automatically after writing.

high_score = 4200
with open("highscore.txt", "w") as score_file:
    score_file.write(str(high_score))
print("High score recorded")

Back to Exercise 20.18.3 · sol_20_18_3.py

Solution 20.18.4 — Writing a daily journal entry

Show the diagnosis and the fix

Bug type: Logical

The file is left open. A with block guarantees the file is closed even if an error occurs.

entry = "Today I walked 10000 steps.\n"
with open("journal.txt", "w") as journal:
    journal.write(entry)
print("Entry written")

Back to Exercise 20.18.4 · sol_20_18_4.py

Solution 20.18.5 — Storing a measured weight

Show the diagnosis and the fix

Bug type: Logical

weight_file is never closed, risking unflushed data. Use a with block to close it safely.

weight_kg = 72.4
with open("weight.txt", "w") as weight_file:
    weight_file.write(f"{weight_kg}\n")

Back to Exercise 20.18.5 · sol_20_18_5.py

Solutions 20.19.1–20.19.5

Solution 20.19.1 — Converting a typed age

Show the diagnosis and the fix

Bug type: Logical

The bare except: hides every error, including programming mistakes. Catch only ValueError, which is the error int() raises on bad text.

text = "12y"
try:
    age = int(text)
    print("Next year you will be", age + 1)
except ValueError:
    print("Please type a whole number")

Back to Exercise 20.19.1 · sol_20_19_1.py

Solution 20.19.2 — Dividing a bill among friends

Show the diagnosis and the fix

Bug type: Logical

A broad except: would swallow unrelated bugs. Catch the specific ZeroDivisionError that division by zero raises.

bill = 90
people = 0
try:
    share = bill / people
    print("Each pays", share)
except ZeroDivisionError:
    print("There must be at least one person")

Back to Exercise 20.19.2 · sol_20_19_2.py

Solution 20.19.3 — Looking up a price

Show the diagnosis and the fix

Bug type: Logical

The bare except: hides all errors. A missing dictionary key raises KeyError, so catch exactly that.

prices = {"apple": 0.5, "banana": 0.3}
item = "cherry"
try:
    print("Price:", prices[item])
except KeyError:
    print("That item is not on the price list")

Back to Exercise 20.19.3 · sol_20_19_3.py

Solution 20.19.4 — Parsing a temperature

Show the diagnosis and the fix

Bug type: Logical

A bare except: masks unrelated problems. float() raises ValueError on non-numeric text, so catch ValueError.

reading = "hot"
try:
    celsius = float(reading)
    print("Fahrenheit:", celsius * 9 / 5 + 32)
except ValueError:
    print("That is not a valid temperature")

Back to Exercise 20.19.4 · sol_20_19_4.py

Solution 20.19.5 — Reading a list position

Show the diagnosis and the fix

Bug type: Logical

The broad except: hides real bugs. An out-of-range index raises IndexError, so catch exactly that.

scores = [88, 92]
try:
    print("Third score:", scores[2])
except IndexError:
    print("There is no score at that position")

Back to Exercise 20.19.5 · sol_20_19_5.py

Solutions 20.20.1–20.20.5

Solution 20.20.1 — Accepting a yes answer

Show the diagnosis and the fix

Bug type: Logical

"YES" == "yes" is False because case differs. Normalize the case first with .lower().

answer = "YES"
if answer.lower() == "yes":
    print("Confirmed")
else:
    print("Not confirmed")

Back to Exercise 20.20.1 · sol_20_20_1.py

Solution 20.20.2 — Matching a chosen color

Show the diagnosis and the fix

Bug type: Logical

favorite.upper() gives "BLUE", which never equals the lowercase "blue". Normalize both sides to the same case, for example .lower() == "blue".

favorite = "Blue"
if favorite.lower() == "blue":
    print("You picked blue")

Back to Exercise 20.20.2 · sol_20_20_2.py

Solution 20.20.3 — Checking a chemical symbol

Show the diagnosis and the fix

Bug type: Logical

"NA" == "Na" is False because the cases differ. Normalize the typed symbol, for example with .capitalize().

symbol = "NA"
if symbol.capitalize() == "Na":
    print("That is sodium")

Back to Exercise 20.20.3 · sol_20_20_3.py

Solution 20.20.4 — Looking up a city name

Show the diagnosis and the fix

Bug type: Logical

The list stores "Paris", so a lowercase "paris" is not found. Compare in a consistent case, for example by lowercasing each city.

cities = ["Paris", "London", "Tokyo"]
search = "paris"
if search in [city.lower() for city in cities]:
    print("City found")
else:
    print("City not found")

Back to Exercise 20.20.4 · sol_20_20_4.py

Solution 20.20.5 — Confirming a unit

Show the diagnosis and the fix

Bug type: Logical

unit.upper() produces "KG", which never equals lowercase "kg". Compare against the same case you converted to, for example .lower() == "kg".

unit = "KG"
if unit.lower() == "kg":
    print("Kilograms")

Back to Exercise 20.20.5 · sol_20_20_5.py

Solutions 20.21.1–20.21.5

Solution 20.21.1 — Printing each planet

Show the diagnosis and the fix

Bug type: Runtime

The loop indexes with i but prints planet, which was never defined, raising NameError. Iterate over the items directly.

planets = ["Mercury", "Venus", "Earth", "Mars"]
for planet in planets:
    print(planet)

Back to Exercise 20.21.1 · sol_20_21_1.py

Solution 20.21.2 — Summing daily sales

Show the diagnosis and the fix

Bug type: Runtime

total + sales adds an integer to the whole list, raising TypeError. Iterate over the items and add each value.

sales = [120, 85, 200, 95]
total = 0
for amount in sales:
    total = total + amount
print("Total sales:", total)

Back to Exercise 20.21.2 · sol_20_21_2.py

Solution 20.21.3 — Greeting each guest

Show the diagnosis and the fix

Bug type: Logical

range(len(guests)) yields the numbers 0, 1, 2, so the greeting prints numbers, not names. Iterate over the guests directly.

guests = ["Ana", "Ben", "Cara"]
for guest in guests:
    print("Welcome,", guest)

Back to Exercise 20.21.3 · sol_20_21_3.py

Solution 20.21.4 — Doubling each measurement

Show the diagnosis and the fix

Bug type: Logical

range(len(...)) makes m the index 0, 1, 2, so it doubles indexes, not values. Iterate over the measurements directly.

measurements = [3, 5, 8]
for m in measurements:
    print(m * 2)

Back to Exercise 20.21.4 · sol_20_21_4.py

Solution 20.21.5 — Listing the ingredients

Show the diagnosis and the fix

Bug type: Logical

item becomes the index 0, 1, 2 instead of the ingredient name. Iterate over the list itself.

ingredients = ["flour", "sugar", "butter"]
for item in ingredients:
    print(item)

Back to Exercise 20.21.5 · sol_20_21_5.py

Solutions 20.22.1–20.22.5

Solution 20.22.1 — Counting items in a basket

Show the diagnosis and the fix

Bug type: Runtime

Assigning len = [4, 8, 15] shadows the built-in len, so len(fruit) tries to call a list and raises TypeError. Rename the variable.

basket = [4, 8, 15]
fruit = "banana"
print("Letters in banana:", len(fruit))

Back to Exercise 20.22.1 · sol_20_22_1.py

Solution 20.22.2 — Totaling a receipt

Show the diagnosis and the fix

Bug type: Runtime

sum = 0 shadows the built-in sum, so sum(prices) tries to call an integer and raises TypeError. Use a different variable name.

running_total = 0
prices = [2.50, 3.00, 1.25]
print("Receipt total:", sum(prices))

Back to Exercise 20.22.2 · sol_20_22_2.py

Solution 20.22.3 — Turning a word into letters

Show the diagnosis and the fix

Bug type: Runtime

list = "abc" shadows the built-in list, so list("hello") tries to call a string and raises TypeError. Rename the variable.

word = "abc"
letters = list("hello")
print(letters)

Back to Exercise 20.22.3 · sol_20_22_3.py

Solution 20.22.4 — Labeling a measurement

Show the diagnosis and the fix

Bug type: Runtime

str = "kilograms" shadows the built-in str, so str(weight) tries to call a string and raises TypeError. Rename the variable.

unit = "kilograms"
weight = 70
print(str(weight) + " " + unit)

Back to Exercise 20.22.4 · sol_20_22_4.py

Solution 20.22.5 — Finding the largest reading

Show the diagnosis and the fix

Bug type: Runtime

max = 9999 shadows the built-in max, so max(readings) tries to call an integer and raises TypeError. Use a different name.

ceiling = 9999
readings = [33.1, 36.5, 31.0]
print("Highest reading:", max(readings))

Back to Exercise 20.22.5 · sol_20_22_5.py

Solutions 20.23.1–20.23.5

Solution 20.23.1 — Splitting a bill exactly

Show the diagnosis and the fix

Bug type: Logical

0.10 + 0.10 + 0.10 is not exactly 0.30 in binary floating-point, so the equality fails. Compare with a small tolerance using round or math.isclose.

import math

share = 0.10
total = share + share + share
if math.isclose(total, 0.30):
    print("The shares add up exactly")
else:
    print("The shares do not add up exactly")

Back to Exercise 20.23.1 · sol_20_23_1.py

Solution 20.23.2 — Adding two distances

Show the diagnosis and the fix

Bug type: Logical

0.1 + 0.2 is slightly more than 0.3 in floating-point, so the exact comparison is False. Use math.isclose to compare with tolerance.

import math

leg_one = 0.1
leg_two = 0.2
if math.isclose(leg_one + leg_two, 0.3):
    print("Distances match")
else:
    print("Distances do not match")

Back to Exercise 20.23.2 · sol_20_23_2.py

Solution 20.23.3 — Checking a measured volume

Show the diagnosis and the fix

Bug type: Logical

0.1 * 3 does not equal 0.3 exactly in floating-point. Compare with a tolerance, for example math.isclose.

import math

pour = 0.1
filled = pour * 3
if math.isclose(filled, 0.3):
    print("Cup is exactly full")
else:
    print("Cup is not exactly full")

Back to Exercise 20.23.3 · sol_20_23_3.py

Solution 20.23.4 — Verifying a percentage

Show the diagnosis and the fix

Bug type: Logical

0.7 + 0.1 is not exactly 0.8 in binary floating-point, so the strict equality fails. Use math.isclose to allow for rounding error.

import math

part_one = 0.7
part_two = 0.1
if math.isclose(part_one + part_two, 0.8):
    print("Percentages add correctly")
else:
    print("Percentages do not add correctly")

Back to Exercise 20.23.4 · sol_20_23_4.py

Solution 20.23.5 — Comparing a savings target

Show the diagnosis and the fix

Bug type: Logical

1.10 * 3 does not land exactly on 3.30 in floating-point. Compare with a tolerance using math.isclose.

import math

weekly = 1.10
saved = weekly * 3
if math.isclose(saved, 3.30):
    print("You reached the target")
else:
    print("You did not reach the target")

Back to Exercise 20.23.5 · sol_20_23_5.py

Solutions 20.24.1–20.24.5

Solution 20.24.1 — Average speed of a trip

Show the diagnosis and the fix

Bug type: Logical

The formula is reversed: speed is distance divided by time, not time divided by distance. Swap the operands.

def average_speed(distance, time):
    return distance / time

print("Average speed:", average_speed(150, 3))  # Average speed: 50.0

Back to Exercise 20.24.1 · sol_20_24_1.py

Solution 20.24.2 — Final price after discount

Show the diagnosis and the fix

Bug type: Logical

A percentage must be divided by 100 first; price * 20 computes a huge discount. Divide the percent by 100 (or test the discount step alone to catch this).

def final_price(price, percent_off):
    discount = price * percent_off / 100
    return price - discount

print("Final price:", final_price(50, 20))  # Final price: 40.0

Back to Exercise 20.24.2 · sol_20_24_2.py

Solution 20.24.3 — Kinetic energy of a moving cart

Show the diagnosis and the fix

Bug type: Logical

The speed must be squared (speed ** 2), but the code multiplies by 2 instead. Testing the squaring step alone would reveal this.

def kinetic_energy(mass, speed):
    return 0.5 * mass * speed ** 2

print("Kinetic energy:", kinetic_energy(2, 3))  # Kinetic energy: 9.0

Back to Exercise 20.24.3 · sol_20_24_3.py

Solution 20.24.4 — Celsius to Fahrenheit

Show the diagnosis and the fix

Bug type: Logical

The formula adds 32, but the code subtracts it. Change - 32 to + 32.

def to_fahrenheit(celsius):
    return celsius * 9 / 5 + 32

print("Fahrenheit:", to_fahrenheit(100))  # Fahrenheit: 212.0

Back to Exercise 20.24.4 · sol_20_24_4.py

Solution 20.24.5 — Average of a list of grades

Show the diagnosis and the fix

Bug type: Logical

The stray - 1 skews the average. Testing the bare sum / len step alone would expose it; remove the - 1.

def average(grades):
    return sum(grades) / len(grades)

print("Average:", average([80, 90, 80, 90]))  # Average: 85.0

Back to Exercise 20.24.5 · sol_20_24_5.py

Solutions 20.25.1–20.25.5

Solution 20.25.1 — Copying a seating chart

Show the diagnosis and the fix

Bug type: Logical

original.copy() is a shallow copy: the inner row lists are still shared, so appending through backup also changes original. Use copy.deepcopy.

import copy

original = [["Ana", "Ben"], ["Cara", "Dan"]]
backup = copy.deepcopy(original)
backup[0].append("Eve")
print("Original:", original)  # Original: [['Ana', 'Ben'], ['Cara', 'Dan']]

Back to Exercise 20.25.1 · sol_20_25_1.py

Solution 20.25.2 — Backing up monthly budgets

Show the diagnosis and the fix

Bug type: Logical

Slicing with [:] copies only the outer list; the nested lists stay shared, so editing saved edits budgets. Use copy.deepcopy.

import copy

budgets = [[100, 200], [300, 400]]
saved = copy.deepcopy(budgets)
saved[1][0] = 999
print("Budgets:", budgets)  # Budgets: [[100, 200], [300, 400]]

Back to Exercise 20.25.2 · sol_20_25_2.py

Solution 20.25.3 — Duplicating a tic-tac-toe board

Show the diagnosis and the fix

Bug type: Logical

list(board) makes a shallow copy whose inner rows are shared, so editing trial changes board. Use copy.deepcopy.

import copy

board = [["X", "O"], ["O", "X"]]
trial = copy.deepcopy(board)
trial[0][1] = "X"
print("Board:", board)  # Board: [['X', 'O'], ['O', 'X']]

Back to Exercise 20.25.3 · sol_20_25_3.py

Solution 20.25.4 — Snapshotting weekly readings

Show the diagnosis and the fix

Bug type: Logical

readings.copy() is shallow, so the nested lists remain shared and editing snapshot also edits readings. Use copy.deepcopy.

import copy

readings = [[1.0, 2.0], [3.0, 4.0]]
snapshot = copy.deepcopy(readings)
snapshot[0][0] = 99.0
print("Readings:", readings)  # Readings: [[1.0, 2.0], [3.0, 4.0]]

Back to Exercise 20.25.4 · sol_20_25_4.py

Solution 20.25.5 — Cloning a recipe with sub-steps

Show the diagnosis and the fix

Bug type: Logical

Slicing with [:] copies only the outer list; the nested step lists are shared, so appending through clone changes recipe. Use copy.deepcopy.

import copy

recipe = [["mix", "stir"], ["bake", "cool"]]
clone = copy.deepcopy(recipe)
clone[1].append("serve")
print("Recipe:", recipe)  # Recipe: [['mix', 'stir'], ['bake', 'cool']]

Back to Exercise 20.25.5 · sol_20_25_5.py

Solutions 20.26.1–20.26.5

Solution 20.26.1 — Collecting quiz answers

Show the diagnosis and the fix

Bug type: Logical

The default sheet=[] is created once and reused, so answers accumulate across calls. Default to None and make a fresh list inside.

def record_answer(answer, sheet=None):
    if sheet is None:
        sheet = []
    sheet.append(answer)
    return sheet

print(record_answer("A"))  # ['A']
print(record_answer("B"))  # ['B']

Back to Exercise 20.26.1 · sol_20_26_1.py

Solution 20.26.2 — Building a grocery list

Show the diagnosis and the fix

Bug type: Logical

The mutable default cart=[] is shared between calls, so items pile up. Use None and create a new list inside the function.

def add_item(item, cart=None):
    if cart is None:
        cart = []
    cart.append(item)
    return cart

print(add_item("milk"))   # ['milk']
print(add_item("eggs"))   # ['eggs']

Back to Exercise 20.26.2 · sol_20_26_2.py

Solution 20.26.3 — Logging a single temperature

Show the diagnosis and the fix

Bug type: Logical

The default log=[] is created once and reused across calls, so readings accumulate. Default to None and build a fresh list.

def log_reading(reading, log=None):
    if log is None:
        log = []
    log.append(reading)
    return log

print(log_reading(21.5))  # [21.5]
print(log_reading(19.0))  # [19.0]

Back to Exercise 20.26.3 · sol_20_26_3.py

Solution 20.26.4 — Tracking a player’s scores

Show the diagnosis and the fix

Bug type: Logical

The shared default board=[] keeps old scores between calls. Use None and create a new list inside the function.

def new_scoreboard(score, board=None):
    if board is None:
        board = []
    board.append(score)
    return board

print(new_scoreboard(10))  # [10]
print(new_scoreboard(20))  # [20]

Back to Exercise 20.26.4 · sol_20_26_4.py

Solution 20.26.5 — Noting one ingredient

Show the diagnosis and the fix

Bug type: Logical

The mutable default items=[] is reused on every call, so ingredients accumulate. Default to None and make a fresh list.

def note_ingredient(name, items=None):
    if items is None:
        items = []
    items.append(name)
    return items

print(note_ingredient("flour"))  # ['flour']
print(note_ingredient("sugar"))  # ['sugar']

Back to Exercise 20.26.5 · sol_20_26_5.py

results matching ""

    No results matching ""