Debugging A: What a Bug Is, Kinds of Errors and Reading a Stack Trace

Lesson goals
  • Define what a Bug is and tell apart a Syntax Error, a Runtime Error and a Logical Error
  • Read a Stack Trace — identify the error type, the message, the file and the line
  • Know the common errors in Python and the cause of each one
  • Understand why a Logical Error is the hardest one to find

What will we learn?

  • What a Bug is.
  • The three kinds of errors: Syntax Error, Runtime Error, Logical Error.
  • How to read a Stack Trace (Traceback), Python’s error message.
  • The most common errors, and what each one says.

Why do we need this?

Developers spend a huge part of their time finding and fixing bugs. The difference between a beginner and a professional is not that the professional has no bugs, but that they read the error message and know within seconds where to look. Beginners see a lot of red text and panic. By the end of the lesson, the red text will become a map.

Explanation from the basics

What is a Bug?

A Bug is a gap between what the program is supposed to do and what it actually does. Sometimes the program crashes, and sometimes it “works” but returns a wrong result.

Why is it called a Bug?

In 1947, engineers of the Mark II computer found a moth (a bug) stuck inside one of the relays, and taped it into the work log with the caption “first actual case of bug being found”. The word was used for technical faults even before that, but this story made it famous.

The three kinds of errors

KindWhen it is discoveredDoes Python report it?Example
Syntax ErrorBefore running (when the syntax is checked)Yes, and no line runsA missing : or bracket
Runtime Error (Exception)While running, on the problematic lineYes, and the lines before it already ranDivision by 0, a variable that does not exist
Logical ErrorNever automaticallyNo! The code runs “successfully”+ instead of -, a reversed condition

Remember the topic on the computer, the operating system and the network? A Syntax Error is caught when Python translates to Bytecode, before execution. A Runtime Error happens when the PVM executes a specific line and fails.

A Logical Error is the most dangerous: no message, no red color, just a wrong result. You find it only if you check that the result is correct.

Stack Trace — the map of the error

When a Runtime Error happens, Python prints a Traceback (also called a Stack Trace): the path the code took until the error.

Traceback (most recent call last):
  File "shop.py", line 9, in <module>
    total = checkout(cart)
  File "shop.py", line 5, in checkout
    return subtotal / count
ZeroDivisionError: division by zero

Read it from the bottom up:

  1. The last line — the type of the error (ZeroDivisionError) and the message (division by zero). Always start here.
  2. The line above it — where exactly: the file shop.py, line 5, inside the function checkout, and the code itself.
  3. Above that — who called this function: line 9, at the top level of the file (<module>).

Why “Stack”? Because every function call is piled onto the stack of calls (the Call Stack). The Traceback shows the pile at the moment of the crash, from the first call (at the top) to the last one (at the bottom).

The common errors in Python

ErrorMeaningCommon cause
SyntaxErrorThe code is not valid syntaxA missing :, brackets, quotes
IndentationErrorWrong indentationMixing spaces, inconsistent indentation
NameErrorA name is not definedA typo, use before definition
TypeErrorAn operation on an unsuitable type"5" + 3, calling a function with the wrong number of arguments
ValueErrorThe right type, an invalid valueint("hello")
ZeroDivisionErrorDivision by zeroA denominator that was not checked
IndexErrorAn index outside the listitems[5] when there are 3 items
KeyErrorA key that does not exist in a dict(we will learn it in the data structures and functions topic)
AttributeErrorThe object has no such attribute/method"abc".push(), a typo in a method name
FileNotFoundErrorA file does not existA wrong path, the Working Directory

Syntax

There is no new syntax. The tool is reading. The pattern of every Traceback:

Traceback (most recent call last):
  File "<file>", line <N>, in <function or <module>>
    <the code on that line>
  ...                                  ← more frames, each a function call
<ErrorType>: <message>                 ← START READING HERE

And of a Syntax Error (without a Traceback, because nothing ran):

  File "<file>", line <N>
    <the code>
        ^                              ← where Python noticed the problem
SyntaxError: <message>

First code example

week-2/bug_report.py:

def average(numbers):
    total = sum(numbers)
    return total / len(numbers)

scores = []
print("Average score:", average(scores))

Output:

Traceback (most recent call last):
  File "C:\dev\kodkod6\week-2\bug_report.py", line 7, in <module>
    print("Average score:", average(scores))
                            ^^^^^^^^^^^^^^^
  File "C:\dev\kodkod6\week-2\bug_report.py", line 3, in average
    return total / len(numbers)
           ~~~~~~^~~~~~~~~~~~~~
ZeroDivisionError: division by zero

Breaking down the code line by line

Reading the Traceback from the bottom up:

Line in the outputWhat it tells us
ZeroDivisionError: division by zeroWhat happened: division by zero.
line 3, in average + return total / len(numbers)Where: line 3, inside average. The ~~~^~~~ marks exactly the division operation.
line 7, in <module> + average(scores)Who called: line 7, at the top level. <module> = “not inside a function”.

Drawing the conclusion: len(numbers) is 0, meaning the list is empty. And who passed an empty list? Line 7 passed scores, which is []. The real bug is that we did not handle the case of an empty list. The line that crashed (3) is the symptom, and deciding what to do with an empty list is the fix:

def average(numbers):
    if len(numbers) == 0:
        return 0
    return sum(numbers) / len(numbers)

Another example

The three kinds of errors on the same function. The function is supposed to calculate a price after a percentage discount.

Syntax Error:

def price_after_discount(price, percent)
    return price - price * percent / 100
  File "discount.py", line 1
    def price_after_discount(price, percent)
                                            ^
SyntaxError: expected ':'

The colon is missing. Python did not run a single line.

Runtime Error:

def price_after_discount(price, percent):
    return price - price * percent / 100

print(price_after_discount("100", 10))
Traceback (most recent call last):
  File "discount.py", line 4, in <module>
    print(price_after_discount("100", 10))
  File "discount.py", line 2, in price_after_discount
    return price - price * percent / 100
                   ~~~~~~~~~~~~~~~~^~~~~
TypeError: unsupported operand type(s) for /: 'str' and 'int'

The syntax is valid, but at runtime price is text. "100" * 10 “works” (it repeats the text 10 times!), but then the division by 100 fails. The ^ points at the /.

Logical Error:

def price_after_discount(price, percent):
    return price - percent / 100

print(price_after_discount(100, 10))
99.9

No error at all! But a 10% discount on 100 should return 90.0. We forgot to multiply by price. Python cannot know what we meant. Only a check against an expected result will reveal it.

Common mistake

Mistake 1: reading the Traceback from the top, and panicking. Always start from the last line: the type of the message. Then the line above it. Most of the time, that is all you need.

Mistake 2: fixing the line that crashed, instead of the cause. In the first example, a “fix” like return total / (len(numbers) + 1) makes the error disappear, but returns a wrong result in every case! That turned a Runtime Error into a Logical Error. Even worse.

Mistake 3: looking for the Syntax Error only on the line shown in the message. Sometimes the problem is on the previous line:

prices = [10, 20, 30
total = sum(prices)
  File "test.py", line 1
    prices = [10, 20, 30
             ^
SyntaxError: '[' was never closed

Python reached line 2, and discovered that the bracket from line 1 was never closed. In new versions the message is precise, but in general: if the marked line looks fine, check the one before it.

Practice

The exercises use only what was taught in this lesson and the lessons before it. Try on your own first, and only then open the solution.

Exercise 1: Which kind of error?

For each snippet — is it a SyntaxError, a Runtime Error or a Logical Error? And what is the problem?

# A
if age > 18
    print("adult")

# B
print(total_price)        # total_price was never defined

# C
def area(width, height):
    return width + height   # should be the area of a rectangle
Show solution
  • A — SyntaxError: the colon : is missing at the end of the if line. No line runs.
  • B — Runtime Error (NameError): the syntax is valid, but at runtime Python cannot find a variable named total_price.
  • C — Logical Error: there is no error at all, but the result is wrong — an area is calculated by multiplying, width * height. You find this error only if you check the result.

Exercise 2: Reading a Stack Trace

Traceback (most recent call last):
  File "shop.py", line 12, in <module>
    print(checkout([20, 30], 0))
  File "shop.py", line 7, in checkout
    return average_price(prices, count)
  File "shop.py", line 3, in average_price
    return sum(prices) / count
ZeroDivisionError: division by zero
  1. What is the type of the error and what is the message?
  2. In which file, on which line and in which function did it happen?
  3. Where did the problematic value come from?
Show solution
  1. ZeroDivisionError: division by zero — division by zero. Read the last line first.
  2. In the file shop.py, line 3, in the function average_price — that is the lowest line in the list.
  3. Read upwards: average_price was called from line 7 in checkout, and checkout was called from line 12 with count = 0. So the problem started in the call on line 12, which passed 0.

Exercise 3: A logical bug in a temperature conversion

def to_celsius(fahrenheit):
    return fahrenheit - 32 * 5 / 9

print(to_celsius(212))
print(to_celsius(32))

The results should be 100 and 0, but something else comes out. Find the bug and fix it.

Expected output:

100.0
0.0
Show solution
def to_celsius(fahrenheit):
    return (fahrenheit - 32) * 5 / 9


print(to_celsius(212))
print(to_celsius(32))
100.0
0.0

Order of operations: multiplication and division come before subtraction, so the original code calculated fahrenheit - (32 * 5 / 9). The brackets force the subtraction to happen first. This is a Logical Error — no error message, just a wrong result.

Exercise 4: TypeError

age = 25
print("Age: " + age)
  1. Which error will you get, and why?
  2. Fix it in two different ways.

Expected output:

Age: 25
Show solution
  1. TypeError: can only concatenate str (not "int") to str — you cannot join text and a number with +.
  2. Two ways:
age = 25
print("Age: " + str(age))
Age: 25
age = 25
print("Age:", age)
Age: 25

The first converts the number to text. The second passes two values to print, and it adds a space between them.

Exercise 5: The average of an empty list

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

print(average([80, 90]))
print(average([]))
  1. What will happen in the second call, and which error will you get?
  2. Fix the function so that for an empty list it returns 0.

Expected output:

85.0
0
Show solution
  1. len([]) is 0, so you get ZeroDivisionError: division by zero — a runtime error.
  2. Check this case before the division:
def average(numbers):
    if len(numbers) == 0:
        return 0
    return sum(numbers) / len(numbers)


print(average([80, 90]))
print(average([]))
85.0
0

Summary

  • Bug = a gap between the desired behavior and the actual behavior.
  • Syntax Error — before running, no line runs. Runtime Error — in the middle of running, with a Traceback. Logical Error — no message, just a wrong result.
  • Read a Traceback from the bottom up: type + message → line and function → who called.
  • The line that crashed is sometimes only the symptom. Look for the cause.
  • Logical Errors are discovered only when you compare a result with an expected value.

Official documentation