Errors, debugging, and how to think
Here is the secret no one tells beginners: errors are normal, and they are on your side. When you write code, the computer does exactly what you typed — not what you meant. The gap between those two things is called a bug (a mistake in the code), and finding and fixing bugs is called debugging. Every working programmer, no matter how senior, hits errors constantly — many times an hour. An error is not a report card on your talent. It is the computer doing you a favor: telling you, precisely, what went wrong and where. Learning to read that message calmly is the single most useful skill in this entire phase, and it is what this lesson is about.
The first thing to understand is that there are two broad kinds of things that go wrong, and they feel completely different.
- 1 · Errors that STOP the program. Also called exceptions or crashes. The program is running, hits something it cannot do, gives up on the spot, and prints a loud message. These are the friendly ones: they shout exactly where they died. Example: trying to grab the 100th item of a 3-item list.
- 2 · LOGIC errors. The program runs to the end with no error message at all — but the answer is wrong. Maybe you added when you meant to subtract. Nothing crashes; the result is just quietly incorrect. These are the sneaky ones, because the computer never tells you. You have to notice.
For the crashing kind, Python hands you a gift called a traceback.
:::note What a traceback is
A traceback is the trail Python prints when it crashes — a few lines tracing the path to the failure. It can look scary because there is a lot of it, but here is the trick: read the very last line first. The last line names the error type (a label like IndexError) and gives a short plain-English message. The lines just above it show the file and the line number of your code that broke, and even reprint that line. So the bottom tells you what went wrong, and the bit above it tells you where.
:::
nums = [10, 20, 30]
print(nums[5])
# Traceback (most recent call last): <- "here comes the trail"
# File "shop.py", line 2, in <module> <- WHERE: file + line number
# print(nums[5]) <- the exact line that broke
# IndexError: list index out of range <- READ THIS LINE FIRST (what + why)
That bottom line says two things: the type is IndexError, and the message is "list index out of range." Translated to plain English: "you asked for a position in a list that does not exist." The list nums only has positions 0, 1, and 2 — there is no position 5. The line above tells you it happened on line 2 of shop.py. With those two facts you already know almost everything you need. Reading the traceback is not optional advice — it is the job.
The common error types in plain English
Each one has a one-line cause and a tiny snippet that triggers it. You do not need to memorize these — but recognizing the name on that last traceback line tells you instantly what kind of mistake to hunt for.
- SyntaxError — you broke the grammar of the language; Python cannot even read the line. (A missing colon, an unclosed quote or bracket.) Example:
if x > 3with no:on the end. - IndentationError — your spacing is off. Python uses indentation (the blank space at the start of a line) to know what is "inside" a loop or
if, so inconsistent spaces confuse it. Example: a line indented 3 spaces where its neighbors use 4. - NameError — you used a variable that does not exist, almost always a typo or a name you never created. Example:
prnt(x)instead ofprint(x), or usingtotalbefore you set it. - TypeError — you used a type (a kind of value, like number vs. text) the wrong way. Example:
"3" + 5— Python will not add text to a number. (Fix: make them match, e.g.int("3") + 5.) - ValueError — the type is right but the value makes no sense for the operation. Example:
int("abc")— that is text, whichint()accepts, but "abc" is not a number it can convert. - IndexError — a list or string position that does not exist:
nums[5]when the list has only 3 items. - KeyError — a dictionary key that is not there:
scores["bob"]when "bob" was never added. - ZeroDivisionError — you tried to divide by zero, which has no answer in math:
10 / 0.
The debugging mindset
Once you know where the program broke, how do you figure out why? The cheapest and most powerful tool in all of programming is the humble print(). Most bugs come from one thing: a variable does not hold what you assume it holds. Assumptions are the enemy. So you print the variable and look at the real value, replacing a guess with a fact. Think of print() as a flashlight you shine into your running program.
total = 0
prices = [5, 10, "15"] # oops: "15" is TEXT, not the number 15
for p in prices:
print("adding:", p) # flashlight: show each value as we go
total = total + p # crashes with TypeError on the "15"
Running this prints adding: 5, then adding: 10, then adding: 15 — and only then crashes. But notice: the last printed value shows up next to the others with no quote marks in the traceback's eyes, yet the crash is a TypeError. The print proves the third item is the string "15", not the number 15 — exactly the type mismatch the error complained about. You found the bug by looking, not guessing. The fix is to make it a real number: 5, 10, 15 (no quotes), or convert with int(p).
:::note Two more techniques worth knowing
- Read the error message literally. It is a sentence written to help you. "list index out of range" means precisely that — do not skim past it.
- Rubber-duck debugging. Explain your code out loud, line by line, to anyone or anything — even a rubber duck on your desk. Saying "this loop should add each price..." forces you to slow down, and you will often catch the mistake mid-sentence.
- And for harder cases there is a real debugger — a tool that lets you pause your program at a chosen line (a breakpoint) and inspect every variable. You do not need it yet;
print()will carry you a long way. :::
A problem-solving loop for any task
Errors are only half the picture. The other half is a calm, repeatable way to approach any task — so you are not staring at a blank screen wondering where to begin. Here is a loop that works for a tiny script or a hard interview problem alike.
- Understand the problem. Say it back in your own words. Be explicit about what goes in (the inputs) and what should come out (the output). If you cannot state that, no code can be right.
- Do a tiny example by hand. Work one small case on paper before touching the keyboard. If the input is
[3, 1, 2], what is the answer, and how did you get it? That is the recipe you are about to type. - Write it in small steps. Build a little, run it, build a little more. Do not write 40 lines blind and hope. Small steps mean small, findable bugs.
- Run it and read the errors. Let the tracebacks guide you to the exact line. Print variables when the answer is wrong but nothing crashed (that is a logic error).
- Test with a few inputs, including edge cases. Try a normal case, an empty one (like an empty list), and a weird one (a single item, a duplicate, a zero). Edge cases are where bugs hide.
As bugs get harder, you will upgrade that loop into hypothesis-driven debugging — the method senior engineers and interviewers grade. It is the same idea (stop guessing, start testing ideas), with one extra discipline: change only one thing at a time.
:::tip The debugging loop (everything on this page + what comes later)
- Reproduce. Make the bug happen on demand with the smallest input that still breaks. If you cannot trigger it reliably, you are debugging blind.
- State the spec. What should happen vs. what does happen? Pinning that gap points the whole investigation.
- Form a hypothesis out loud. One specific guess: "the loop starts at index 1, so it skips the first element." Not "something is wrong with the loop."
- Test it cheaply. A
print(), a hand-trace of one input, or a tiny test that fails only if your guess is right. - Fix one thing, re-verify. One edit, one prediction, one re-run. Then check edge cases (empty, single item, boundary).
This loop works on a ten-line script, a 40-file repo, and code an AI assistant drafted five minutes ago. The tools grow (grep, git blame, traces); the discipline does not. Later lessons add time pressure and unfamiliar codebases — but you already have the core method here. :::
Industry names for what you just learned
Same ideas — what you'll hear in standup, PRs, and interviews:
- Repro — reproduce the bug on command; minimal repro — smallest case that still fails.
- Stack / traceback — the crash printout; "read the stack" starts at the last line.
- Printf debugging — using
print()to replace guesses with facts. - Shotgun debugging — random edits without a theory. Hypothesis-driven debugging — the opposite.
- Rubber-duck debugging — explain the code out loud until you spot the mistake.
- Logic error — runs fine, wrong answer; no traceback. Regression — worked before, broken now.
Why it matters
You made it — this is the bridge. You now have the core vocabulary of programming: variables, types, decisions (if), loops, functions, the containers (lists, dictionaries, sets, and strings), and how to debug. Errors come in two flavors — crashes that hand you a traceback (read it last-line-first for the what, then the line above for the where) and silent logic errors you catch by printing real values and comparing them to what you expected. Wrap that in a repeatable loop — understand, try a tiny example, build in small steps, read the errors, test the edge cases — and you have the method senior engineers and interviewers actually grade. That is genuinely the foundation everything else is built on, and it is everything this course set out to teach.
Where this leads: reading tracebacks is step one; debugging real code (and interview debugging rounds) builds on it. See Where this leads next.