Chapter 04 · Section III · 24 min read
Debugging: reading errors and asking good questions
Almost every program you write will break before it works. The skill of debugging is not memorising fixes — it is reading the error message slowly, believing what it says, and asking the right question when you cannot solve it alone.
A confession that experienced engineers tell beginners and forget to repeat to themselves: most of programming is debugging. You will write code. It will break. You will fix it. You will write more code. It will break differently. You will fix it again. The shape of this work is not pathology — it is the work. The skill that separates a builder who is back at it in five minutes from one who is stuck for two hours is how they read the error. That is what this section teaches.
The anatomy of a Python traceback
A Python error message — called a traceback — has more structure than it first appears. A typical one:
Traceback (most recent call last):
File "/Users/nischal/nepse-alerts/run.py", line 12, in <module>
total = total_by_vendor(transactions, "Khalti")
File "/Users/nischal/nepse-alerts/run.py", line 6, in total_by_vendor
running = running + t["amont"]
KeyError: 'amont'
Read it from the bottom up:
- The last line —
KeyError: 'amont'— names the error type and gives a one-line cause. This is almost always where the answer is. - The lines above — each
File ... line ...— is the call stack: the path of function calls that led to the error, from the deepest call at the bottom to the outermost at the top. - The very first line (“most recent call last”) is a hint about how to read the stack: the deepest call — the one that actually failed — is the bottom one. The top frames are just how Python got there.
In the example, KeyError: 'amont' means a dictionary was asked for the key 'amont' and did not have it. Look at the line directly above: t["amont"]. Typo. Should be "amount". Fix it in line 6 and the program runs.
That is what most debugging looks like. A typo, a wrong type, a missing import, an off-by-one — and the message tells you which.
A short field guide to common errors
The errors you will meet most often in Course 01:
SyntaxError — Python could not even parse the line. Usually a missing colon at the end of def / if / for, an unclosed quote, an unclosed bracket. The line number is sometimes the line after the real problem (because Python reads to the end of the offending construct before complaining), so glance up too.
IndentationError — you mixed tabs and spaces, or your indents are inconsistent. Set your editor to use four spaces and convert tabs to spaces; the error vanishes.
NameError: name 'X' is not defined — you used a name Python has never seen. Common causes: typo (Print vs print), forgot an import, used a variable before assigning it.
TypeError: ... — you used a value of the wrong type. '2105.4' + 100 is the canonical case — string plus int. Read the message; it usually names the two types involved.
KeyError: 'x' — a dictionary was asked for a key it does not have. Almost always a typo or a missing key in the source data.
IndexError: list index out of range — you indexed a list past its end. expenses[5] on a list of three items. Check len(...).
AttributeError: 'X' object has no attribute 'Y' — you called .something() on a thing that does not have a something method. Sometimes a typo (.uppe() instead of .upper()), sometimes the variable is not what you think it is — print its type(...) and read what it actually holds.
FileNotFoundError — the file is not where Python is looking. Check the path, then check what folder Python is running from (in a notebook, import os; print(os.getcwd())).
ImportError / ModuleNotFoundError — the library you tried to import is not installed in this environment. Either run pip install ... after activating the right virtual environment, or check that you activated it at all.
If you can name nine of those at sight, you are most of the way to being a confident Python reader.
When the message is not enough: print debugging
Sometimes the error message points to a line, and the line looks fine, and the surrounding code looks fine, and you have no idea what is happening. The oldest debugging technique in the world still works:
print("transactions:", transactions[:3])
print("type:", type(transactions))
print("first row:", transactions[0])
print lets you ask “is this thing what I think it is?” at any point in the program. Drop a few prints around the suspect line and run again. You will usually find that one variable is None when you expected a list, or a string when you expected a number, or empty when you expected three rows.
In a notebook, you can do this more interactively: split the failing function into a cell, run each line separately, inspect what each step produces. Notebooks are debugging environments by design.
Asking good questions when you are stuck
Sometimes the error is genuinely hard, and you need help. Whether you are asking a colleague, a forum, or a language model like Claude or ChatGPT, the question you ask shapes the answer you get. A bad question:
“My code doesn’t work. What’s wrong?”
A good question has three parts:
- What you were trying to do — in one sentence. “I’m reading a CSV of NEPSE prices and computing the average close.”
- The code itself — the smallest possible version that still shows the problem. Strip away everything that is not relevant. If your real program is 200 lines, your question should not include 200 lines.
- The exact error message — pasted in, not paraphrased. The error message is information; do not throw it away.
That is the format of a question that gets a useful answer. The same format works for Stack Overflow, for a friend in Patan, and for an AI assistant.
I'm reading a CSV of NEPSE prices and computing the average close.
I have:
import pandas as pd
prices = pd.read_csv("data/prices.csv")
print(prices["close"].mean() + 100)
This raises:
TypeError: can only concatenate str (not "int") to str
The CSV has a header row "date,close" and rows like "2026-06-23,2105.4".
What am I doing wrong?
A question shaped like that gets a one-line answer. A question shaped like “my code doesn’t work” gets either silence or a tutorial that may not apply.
When to ask an AI, and how
Modern AI assistants are unusually good at debugging — they have read more error messages than any human alive. A useful workflow:
- Read the last line yourself first. If it is obvious, do not bother asking; you will learn faster by fixing it.
- If you are stuck after two minutes, paste the error and the relevant code into Claude, ChatGPT, or your assistant of choice, framed using the three-part question above.
- Read the answer carefully and apply it manually, not by copy-pasting blindly. The answer is a hypothesis to test, not gospel.
- If it does not work, paste the new error back, and explain what you tried. AI assistants converse much better than they one-shot.
The risk is becoming dependent on the AI for things you should be learning. The mitigation is the first step — always read the message first, give yourself a fair chance, then escalate.
Check your understanding
Quick check
—You see this traceback: Traceback (most recent call last): File 'run.py', line 14, in <module> print(prices['Open'][0]) KeyError: 'Open' What does it most likely mean?
Quick check
—A junior engineer pings you with: 'my code doesn't work, help'. Based on this section, what is the most useful immediate response?
What comes next
Three habits in place — clean rooms, version control, calm error-reading — you are ready for the most exciting part of Course 01: actually calling a model. In Chapter 5 you will make your first API call to a language model, see a few hundred tokens of text come back, and start to understand what “building on top of AI” really means in practice. The shoulders we are about to stand on are very tall.