Debugging in Python: What You’re Probably Doing Wrong (and How to Fix It)
Most of us don’t learn debugging — we learn how to Google error messages. That works for a while. But the first time your code breaks in a…
Debugging in Python: What You’re Probably Doing Wrong (and How to Fix It)

Blog Thumbnail
Most of us don’t learn debugging — we learn how to Google error messages. That works for a while. But the first time your code breaks in a way that Google can’t help, you suddenly realize something’s missing.
Debugging isn’t a feature, or a tool, or a once-in-a-while activity. It’s a habit. It’s a mindset. And knowing how to read errors, run quick checks, observe your program mid-execution, and test theories is what makes code maintainable — not just technically, but mentally.
This post isn’t about fancy techniques. It’s about five simple debugging habits every developer needs in Python, based on what actually happens when you hit an error mid-sprint, with a deadline in your ear, and a stacktrace blinking in front of you.
Let’s go.
1. Read the Traceback From the Bottom Up
Here’s a simple script:
print(cat)
And here’s what Python tells you:
Traceback (most recent call last):
File "example.py", line 1, in <module>
print(cat)
NameError: name 'cat' is not defined
Too many people glance at the top, get overwhelmed, and bounce. But the key info is always near the bottom.
- File name + line number → where it broke
- Error type → which category (NameError, TypeError, etc.)
- Message → usually human-readable, often the actual fix
Learn to read these without panic. Python isn’t trying to confuse you. It’s being helpful — just not holding your hand. Calmly walk through the lines and reconstruct what happened.
In the example above? Python’s just saying: “You used a variable before you gave it a value.”
So fix it:
cat = "Siamese"
print(cat)
Tracebacks are not punishment. They’re communication. Don’t skim. Read. Quietly, carefully, intelligently.
2. Don’t Underestimate print()
Yes, print(). It’s still relevant.
If something is off — not crashing, just wrong — add a few print statements. Log the progress of your variables. Compare what you think is being stored with what actually is.
Let’s say you have this function:
def process_fruits(items):
return [item.capitalize() for item in items]
Then you call it with:
fruits = ["apple", "BANANA", 7]
result = process_fruits(fruits)
Crash. You get:
AttributeError: 'int' object has no attribute 'capitalize'
Now you could guess. Or you could add this:
print(items)
Now it works. Simple. Elegant. Fix found by observing reality, not by theorizing endlessly.
Use print to gather facts.
3. Insert breakpoints to Pause and Think
Running code top-to-bottom isn’t always enough. Sometimes, you want to pause it, look around, poke at values.
That’s what breakpoints are for.
Python makes this especially easy with its built-in function:
breakpoint()
Put that inside a loop, condition, or just before anything that’s giving you trouble.
def double_items(data):
results = []
for item in data:
breakpoint()
results.append(item * 2)
return results
Now, when you run this, execution stops at breakpoint. You can type commands. Check variables. Try different expressions. Step forward line by line. This is where the bug often reveals itself.
In VS Code or PyCharm, visual breakpoints work too. But in scripts and quick runs, nothing beats just adding breakpoint() where you need it.
It’s the difference between watching your code work in silence — and actually asking it questions in real time.
4. Tests Aren’t Just About Coverage. They’re About Catching Yourself Before You Lie
Testing gets a bad rap. People think it’s slow or bureaucratic.
Here’s a better framing: tests are just your future self talking to your current self, saying: “Hey, if this ever changes, let’s make sure it still does what it was supposed to.”
Take the fruit capitalization function again:
def process_fruits(items):
return [item.capitalize() if isinstance(item, str) else "" for item in items]
You want to make sure it handles:
- empty lists
- normal strings
- all caps
- mixed types
So write this:
import unittest
class TestFruits(unittest.TestCase):
def test_basic(self):
self.assertEqual(process_fruits(["apple", "BANANA"]), ["Apple", "Banana"])
def test_non_string(self):
self.assertEqual(process_fruits(["apple", 7]), ["Apple", ""])
Run it:
python -m unittest test_fruits.py
These tests act like a leash. If someone tweaks the function later and breaks a case, the test fails — loudly.
You don’t write tests because you’re scared of mistakes. You write them because you’re not afraid to admit you might make some.
5. Know Your Error Types
After enough tracebacks, you start seeing patterns in Python’s exceptions:
- NameError → variable doesn’t exist
- TypeError → wrong type for the operation
- AttributeError → object doesn’t have that method or property
- SyntaxError → didn’t write valid code
Each has a tone. A fingerprint.
When you see:
23 + "4" # TypeError
you know what happened. Python doesn’t autocast like JavaScript does.
Be aware of what Python expects — and what it won’t give you leniency on. These error types are not random; they’re signaling where to look and how deep.
What About Tests to Reproduce Bugs?
Let’s say you found a weird bug with someone passing None into your format function. Instead of patching it blind, write a regression test:
def test_reject_none():
try:
format_username(None)
except TypeError:
pass
else:
raise Exception("Expected TypeError")
That’s safety. Not just for this version of the code, but for every future edit too.
Patterns Start Showing Up
After some time, debugging becomes less about the tools and more about pattern recognition:
- Crashes at runtime? Read the traceback.
- Output feels wrong? Use print.
- Logic behaves differently than expected? Drop a breakpoint.
- Want insurance against future regressions? Write a test.
If someone says debugging is hard, they probably haven’t slowed down enough to read the clues.
Errors are data. And debugging is just investigation — not punishment.
Final Thoughts
Nobody writes perfect code. The question isn’t whether you’ll run into errors — it’s whether you know how to respond when you do.
Tracebacks, print statements, breakpoints, tests — these aren’t hacks. They’re your toolbox. All lightweight. All free.
What separates beginners from working developers isn’t syntax. It’s knowing how to dig when things don’t go as planned.
Learn to debug calmly. Slowly. Like you’re reverse-engineering a bike trying to figure out why it won’t turn right.
Because the code is telling you something. Always.
You just need to start listening.
Thanks a lot for reading this.
I always enjoy hearing what people think, so if something here stood out to you or you just want to share your thoughts, drop a comment. I’m always around to chat.
If you want to stay in touch or see more of what I’m doing, you can find me here:
- ▶️ YouTube: youtube.com/@yash0307jain
- 🎯 Topmate: topmate.io/yash0307jain
- 🔗 LinkedIn: linkedin.com/in/yash0307jain
- 💻 GitHub: github.com/yash0307jain
Let’s keep learning, creating, messing up, fixing things, and growing together.
메타데이터
- post_id
- 5918d4a5c150
- slug
- debugging-in-python-what-youre-probably-doing-wrong-and-how-to-fix-it-5918d4a5c150
- url
- https://medium.com/algomart/debugging-in-python-what-youre-probably-doing-wrong-and-how-to-fix-it-5918d4a5c150
- canonical_url
- https://medium.com/algomart/debugging-in-python-what-youre-probably-doing-wrong-and-how-to-fix-it-5918d4a5c150
- author_url
- https://medium.com/@yashjaincodex
- status
- ok
- fetched_at
- 2026-06-09 15:37:30