Your code doesn't work. You've stared at it for twenty minutes. You've changed random things hoping something sticks. Nothing sticks. You're starting to wonder if you're cut out for this.
Take a breath. Every developer — from the intern to the staff engineer with 20 years of experience — has been exactly where you are right now. The difference isn't that experienced developers write bug-free code. It's that they have a system for finding and fixing bugs. And that system is entirely learnable.
First, a Mindset Shift
Before we get into techniques, let's reframe what debugging actually is. Most beginners treat bugs as evidence that they did something wrong — proof that they're bad at coding. Experienced developers see bugs differently: bugs are information. Every error message, every unexpected output, every crash is your program telling you something about the gap between what you intended and what you actually wrote.
Debugging isn't a detour from "real" programming. It is programming. Professional developers spend somewhere between 35% and 50% of their working time debugging. It's not a sign you're doing something wrong — it's literally the job.
Step 1: Read the Error Message (Really Read It)
This sounds obvious, but beginners almost universally skip error messages. They see a wall of red text, panic, and start randomly editing their code. Stop. The error message is the most important clue you have.
Let's break down what a Python error message actually tells you:
- The traceback: This is the trail of function calls that led to the error. Read it from bottom to top. The last line tells you what went wrong. The lines above it tell you where.
- The error type:
TypeError,NameError,IndexError— each type tells you the category of problem. Once you learn what each type means, you can immediately narrow down what to look for. - The error message: The text after the error type is Python trying to explain the problem in English. It's not always clear, but it's always worth reading.
- The line number: Python tells you exactly which line caused the crash. Go to that line. But also look at the line above it — sometimes the real mistake is on the previous line (especially with missing closing brackets or colons).
Step 2: Reproduce the Bug Consistently
Before you try to fix anything, make sure you can reliably trigger the bug. Run your code again. Does the same error happen? With the same input? This might seem like a waste of time, but it's critical. You need to know that when you change something, you can test whether your fix actually worked.
If the bug only happens sometimes, pay attention to what's different between the times it works and the times it doesn't. The difference is usually the key to understanding the problem.
Step 3: The Humble Print Statement
There are fancy debugging tools out there. Breakpoints, step-through debuggers, watch variables. They're great. But for beginners, the single most effective debugging tool is one you already know: print().
The idea is simple: you suspect the bug is somewhere in your code, but you're not sure where. So you add print statements to show you what's happening at each step. Think of it as making the invisible visible.
What to print
- Variable values:
print(f"x = {x}")— Is the variable what you expect it to be? - Variable types:
print(type(x))— A shocking number of bugs come from a variable being a string when you expected an integer, or a list when you expected a single value. - Flow checkpoints:
print("reached point A")— Is your code even reaching the line you think it's reaching? Maybe an early return or a conditional is sending it down a different path. - Loop iterations:
print(f"loop iteration {i}: value = {val}")— Is the loop running the right number of times? Are the values changing the way you expect?
Pro tip: label your print statements. Don't just write print(x). Write print(f"x after the loop: {x}"). When you have five print statements firing, you need to know which one is which.
Step 4: Binary Search Your Bug
If you have a lot of code and you're not sure where the bug is, don't start from the top and check every line. Use a binary search approach:
- Add a print statement roughly in the middle of your code.
- Is the output correct at that point? If yes, the bug is after that line. If no, the bug is before it (or on that line).
- Repeat, cutting the search area in half each time.
This sounds mechanical, but it's incredibly efficient. Instead of checking 100 lines one by one, you can find the culprit in about 7 steps.
The 10 Most Common Python Errors (and What They Mean)
You'll encounter these over and over. Bookmark this section.
- SyntaxError: invalid syntax — You've written something Python can't parse. Check for missing colons after
if/for/defstatements, unmatched parentheses, or missing quotes. - IndentationError — Python uses indentation to define code blocks. Make sure you're consistent with tabs vs. spaces (use spaces — 4 of them).
- NameError: name 'x' is not defined — You're trying to use a variable that doesn't exist yet. Usually a typo, or you forgot to define it.
- TypeError — You're using the wrong type. Trying to add a string to an integer, calling something that isn't a function, passing the wrong number of arguments.
- IndexError: list index out of range — You're trying to access an element that doesn't exist. Remember, Python lists start at index 0, so a list with 5 items has indices 0 through 4.
- KeyError — You're trying to access a dictionary key that doesn't exist. Use
.get()instead of square brackets if the key might be missing. - AttributeError — You're calling a method that doesn't exist on that object. Often means the variable isn't the type you think it is.
- ValueError — The value is the right type but wrong content. Like trying to convert the string "hello" to an integer.
- FileNotFoundError — The file path is wrong. Double-check the path and make sure the file actually exists where you think it does.
- ModuleNotFoundError — You're importing something that isn't installed. Run
pip install module_nameto install it.
When You're Truly Stuck
Sometimes you've tried everything and you're still stuck. Here's a graduated approach:
- Walk away. Seriously. Take a 10-minute break. Walk around. Your brain continues processing problems in the background, and many developers report having breakthroughs in the shower, on a walk, or making coffee. This isn't mystical — it's how your brain's diffuse thinking mode works.
- Rubber duck debugging. Explain your code, line by line, to an inanimate object (a rubber duck, a houseplant, your cat). The act of articulating what each line should do often reveals where your assumption diverges from reality.
- Search the exact error message. Copy the error message and paste it into a search engine. You are almost never the first person to encounter a given error. StackOverflow exists because this problem is universal.
- Ask for a hint, not an answer. Whether you're asking a friend, a forum, or an AI tool, phrase your question to get guidance, not a solution. "What should I look at?" teaches you more than "Can you fix this for me?"
Building the Debugging Habit
Debugging is a skill, which means it gets better with practice. Here are habits that compound over time:
- Test early and often. Don't write 50 lines of code and then run it for the first time. Write 3-5 lines, run them, verify they work, then add more. Small increments mean small bugs.
- Read your code before running it. Pretend you're the computer. Walk through each line mentally. What value does each variable hold? Does the logic flow make sense?
- Keep a bug journal. When you fix a bug, write down what happened and how you found it. You'll start noticing patterns in the kinds of mistakes you make — and you'll fix them faster every time.
- Version control your experiments. Before making big changes to fix a bug, save a copy of your working code. That way you can always get back to a known state if your "fix" makes things worse.
The Truth About Bugs
Here's something that might be comforting: bugs never fully go away. As you get better, the bugs change — they become subtler, more interesting, sometimes even fascinating. But they never stop. Every piece of software that has ever existed has had bugs. The International Space Station has had bugs. Self-driving cars have bugs. The app you're using to read this article probably has bugs.
The goal isn't to write perfect code. The goal is to build the detective skills to find and fix problems efficiently. And every bug you squash today makes you a little bit better at squashing the next one.
So the next time your code breaks, don't panic. Read the error message. Add a print statement. Follow the trail. You've got this.
Related Articles
Get unstuck with AI guidance.
When you're stuck on a bug, Aximon's AI tutor helps you think through it — with hints, not answers.
Join the Waitlist