When Code Breaks

A guide for the moment when the program doesn't do what you wanted — which is almost always.

Fundamentals 13 min Beginner April 26, 2026

The moment your code produces a wall of red text for the first time, your instinct is to panic. But that red text — the traceback — is actually the most helpful thing Python can give you. It tells you exactly what went wrong, exactly where, and exactly why.

The real danger is not when Python screams at you — it is when Python says nothing at all but your results are quietly, subtly wrong. This article teaches you to classify Python errors into three types, read tracebacks like a detective reads a crash report, and write safety nets that keep your programs running.

Three Types of Errors

Error Types in Python

AnalogyDefinition
Think of a recipe in a kitchen. A syntax error is a recipe with a word the cook cannot read: "Add 200g of sltz." The cook stops immediately — the instruction is incomprehensible. A runtime error is an instruction that sounds clear but fails in practice: "Divide the dough into portions based on the number of eggs." If there are zero eggs, the cook is stuck — division by zero. A logic error is a recipe that is perfectly readable and executable but wrong: "Add 200 kilograms of salt." The cook follows the instruction faithfully, and the result is inedible — but nobody told the cook anything was wrong.

The recipe analogy breaks in two places: A real cook would question "200 kilograms of salt" — they have common sense. Python has no common sense and executes any logically valid instruction. Also, a cook can taste the result, but code needs systematic testing.

Syntax Error Stops before execution. Missing parentheses, colons, wrong indentation.
Runtime Error Crashes during execution. Produces a traceback with error message.
Logic Error Runs without warning. Produces wrong results. Most dangerous.

Three Errors, One Program

The same temperature converter can fail in all three ways:

# Syntax error: missing parenthesis
celsius = (fahrenheit - 32 * 5 / 9
# SyntaxError: Python refuses to start

Python cannot even run the code — the parenthesis is missing.

# Runtime error: user enters text instead of number
user_input = input("Temperature: ")
celsius = (int(user_input) - 32) * 5 / 9
# ValueError if user types "warm" instead of a number

The code is syntactically correct but crashes when the input is not a number.

# Logic error: + instead of - (runs but wrong results)
celsius = (fahrenheit + 32) * 5 / 9
# No error, no crash, but the result is wrong!

Python does not complain because the code is syntactically correct and executable. But the formula is wrong (+ instead of -), and the results are incorrect.

No Error Does Not Mean No Problem

The most dangerous misconception: "My code runs without error messages, so it must be correct." Logic errors produce no messages. Your code runs and produces results — they are just wrong. The only protection: test with known inputs and check whether the expected results come out.

Reading the Traceback: Your Error Map

Traceback

AnalogyDefinition
A traceback is like a crash report after a car accident. At the bottom is the crash itself: "Vehicle hit a wall at intersection X" (the error type and message). The section above describes the circumstances: "Vehicle was traveling at 80 km/h on Main Street, line 47" (the failing code line). The sections above trace the journey: "Vehicle departed from parking garage at 8:00 AM, turned onto Highway 5" (the chain of function calls).

The analogy breaks at an important point: A real crash report is written chronologically from top to bottom. Python's traceback shows the most recent call at the bottom ("most recent call last"). That is why you must learn to read from bottom to top.

Reading a Traceback: 4 Steps

1
Read the last line: error type and message (e.g. ValueError: invalid literal)
2
Read the line above: which code line triggered the error?
3
Follow the call chain upward: which function called which?
4
Find the root cause: which function passed the problematic value?

A Traceback in Practice

def get_age():
    return int(input("Your age: "))    # user types "twenty"

def greet_user():
    age = get_age()
    print(f"You are {age} years old.")

greet_user()
Traceback (most recent call last):
  File "app.py", line 7, in <module>       # 3. Program started here
    greet_user()
  File "app.py", line 5, in greet_user     # 2. greet_user called get_age
    age = get_age()
  File "app.py", line 2, in get_age        # 1. get_age crashed HERE
    return int(input("Your age: "))
ValueError: invalid literal for int() with base 10: 'twenty'

Read from the bottom: ValueError tells you the error type. The line above shows that int(input(...)) in get_age() crashed. The lines above trace the path: greet_user() called get_age(), and the main program called greet_user().

Do Not Edit Randomly

A common mistake: ignoring the traceback and editing code at random. The traceback tells you exactly where and what went wrong. Read it from bottom to top, and you save yourself hours of guessing.

Exception Handling: Writing Safety Nets

Exception Handling (try/except)

AnalogyDefinition
Think of a skydiver jumping from a plane. The jump itself is the try block — the normal program flow. If the main parachute fails to open (an error occurs), the skydiver pulls the reserve parachute — the except block catches the specific problem. The finally block is the landing: it happens regardless of which parachute was used, and includes cleanup.

The analogy breaks because a skydiver has exactly one reserve parachute, but Python allows multiple except blocks for different exception types — like separate reserves for different failure modes.

The try/except Flow

1
try block executes: the "risky" code runs normally
2
Exception occurs: Python looks for a matching except block
3
except block responds: show error message, retry, or use default value

Practice: Robust Input

def get_number(prompt):
    while True:
        try:
            return float(input(prompt))
        except ValueError:
            print("That's not a valid number. Try again.")

temperature = get_number("Temperature in Fahrenheit: ")
celsius = (temperature - 32) * 5 / 9
print(f"{temperature}°F = {celsius:.1f}°C")

The function keeps asking for a number until the user provides a valid one. The try block attempts the conversion, and the except block catches the ValueError with a friendly message.

except: pass Is Dangerous

The most dangerous pattern in Python: except: pass. It silently swallows all errors, including real bugs like typos in variable names, making them invisible.

# DANGEROUS: swallows ALL errors
try:
    result = calculate_price(item)
except:
    pass    # Silence. No error visible.

# CORRECT: catch only the expected error
try:
    result = calculate_price(item)
except ValueError:
    print("Invalid price, please check.")

A bare except: pass catches everything — NameError (typos), TypeError (wrong arguments), and even KeyboardInterrupt (Ctrl+C). You will never know your code has a bug.

ValueError int("hello") — wrong value for the type
TypeError "5" + 3 — wrong type for the operation
FileNotFoundError open("missing.csv") — file does not exist
KeyError data["key"] — dictionary key not found
IndexError list[10] on a 3-element list — index out of range
ZeroDivisionError 100 / 0 — division by zero

print() debugging: Add print() statements to check variable values at different points in your code. This lets you trace step by step where values diverge from what you expect.

assert statements: Write assert result > 0, "Result should be positive" in your code. If the condition is not met, Python crashes on purpose and shows you the spot. This turns silent logic errors into loud ones.

Rubber duck debugging: Explain your code line by line to an inanimate object (a rubber duck, a mug, a stuffed animal). The act of putting every step into words forces you to think through each step, and you often discover the error in the process.

Interactive: What Is My Error?

Answer the questions in the diagnosis tree to find out which error type you are dealing with and which debugging strategy helps best. Notice how the three error types lead to different solution approaches.

?

What happens when you run your code?

Key Takeaways

  1. Python errors come in three types: syntax errors (code unreadable, caught before execution), runtime errors (code crashes, produces a traceback), and logic errors (code runs but produces wrong results — most dangerous because silent).
  2. A traceback is read from bottom to top: the last line names the error type, the line above shows the failing code, and the lines above trace the call chain.
  3. try/except blocks catch predictable errors and respond gracefully instead of crashing. Always catch specific exceptions like ValueError — never use bare except:.
  4. except: pass is the most dangerous pattern in Python — it silently swallows all errors, including real bugs, making them invisible and impossible to debug.
  5. "No error message" does not mean "no error." Logic errors produce correct-looking output that is subtly wrong. Always test with known inputs and expected outputs.

Quiz: Debugging

Question 1 / 4
Not completed

Which error type runs without error messages but produces wrong results?

Select one answer
Answer Key: 1) C · 2) B · 3) B · 4) B

Checkpoint: Do You Understand?

  • Your code runs without error messages but prints 0 instead of 100. What type of error is this, and why is it particularly dangerous?
  • You see a traceback whose last line reads "KeyError: 'name'". Where do you look first, and what do the lines above tell you?
  • Write code that asks the user for a number and keeps asking until a valid input comes. Why is except: pass not a good solution here?