7 Python Debugging Techniques Every Beginner Should Know



Image by Author | Canva

 

If you’ve ever spent hours staring at your Python code wondering “Why isn’t this working?”, welcome to the club. As a beginner, you might feel frustrated by silent failures, confusing stack traces, or strange outputs. Debugging is the art of finding and fixing those bugs — and honestly, it’s where most of the real learning happens. While mistakes are unavoidable, getting better at debugging can save you hours (and a lot of frustration). In this post, I’ll walk you through 7 practical debugging techniques I wish I knew earlier. These are simple, effective, and will seriously improve your coding instincts. Each tip will explain a common mistake beginners make and show how the technique helps catch or fix it. Let’s get into it.

 

1. Print Statements and Logging

 
Most newbies’ first instinct is to put print() statements in the code to see what’s happening. For example, printing variable values or a “checkpoint” message inside loops can quickly reveal unexpected values. A common beginner mistake is forgetting to update or remove these prints, or not printing enough context: e.g. doing print(x) without a label can be confusing. You can make print debugging clearer by adding context, like print(f"x is x"). However, professional code generally uses the logging module instead of raw prints. Here’s how you can set it up:

import logging
logging.basicConfig(level=logging.DEBUG)
logging.debug("something happened: %s", var)

 
This way you get timestamps and levels, and you can turn logging on/off or redirect it to files. In short, use print() for a quick peek as it can solve 80% of beginner bugs, but learn to log. Beginners often waste time chasing invisible state changes; well-placed prints or logs make the program’s flow visible.

 

2. Read Error Messages (Stack Traces)

 
When Python crashes, it spits out a stack trace – a traceback of calls leading to the error. Don’t panic! Read it carefully. The last line is the most important: it shows the error type and message. Beginners often ignore the top of the trace, but you should read from the bottom up. Check the error type (e.g. ValueError, IndexError) and message – they usually tell you exactly what went wrong (like “list index out of range” or “NameError: name ‘foo’ is not defined”). Then look at the filenames and line numbers above to see where in your code the error occurred. For example, if you see “NameError: name ‘nam’ is not defined”, you know you spelled a variable wrong in the code. In short, let Python’s own error report guide you. Googling the exact error message often finds answers from others who had the same problem.

 

3. Use Assertions (Sanity Checks)

 
Sometimes errors occur not because of typos but because data unexpectedly violates assumptions. Python’s assert statement can catch those bugs early. An assert is like saying “this must be true here” – if it isn’t, Python raises an AssertionError. For example:

value = get_value()
assert value >= 0, "value should never be negative"

 
This immediately signals if value ever goes wrong. Beginners often forget to check inputs and end up with weird downstream errors. Use asserts into your functions to validate key assumptions (e.g. input ranges, non-empty lists, correct types). If an assert fails, the trace will show you exactly which assumption was broken, making the bug much easier to locate.

 

Understand Variable Scope (Global vs. Local)

 
Bugs often happen because a variable is not what you think it is. In Python, variables defined inside a function are local to that function, unless explicitly declared global. For example:

count = 0

def increment():
    count += 1   # Oops! This causes an error
    return count

increment()

 

Running this code gives an UnboundLocalError: local variable 'count' referenced before assignment. Even though count is defined outside, the count += 1 line makes Python treat count as local, and it’s referenced before being set. To fix this, you could declare global count inside the function, or better yet, avoid mixing scopes. A clearer example:

x = 5
def foo():
    print(x)      # This works (prints 5)

foo()

 

Here x is read in the function and Python finds the global x. But if you try to assign to x:

x = 5
def foo():
    x = x + 1     # Error! Python now thinks x is local
    print(x)

foo()

 

This fails. The rule is: assigning to a name in a function makes it local, so if you meant the global, Python is confused. The fix might be to rename the variable inside the function or use global. Knowing this can help avoid mysterious “variable not defined” errors. Always check whether you’re inadvertently using or modifying a global name inside a function.

 

5. Avoid Mutable Default Argument Pitfalls

 
One classic Python gotcha involves default arguments in functions, especially mutable ones like lists or dictionaries. For instance:

def add_item(item, my_list=[]):
    my_list.append(item)
    return my_list

print(add_item("apple"))  # ['apple']
print(add_item("banana")) # ['apple', 'banana'] – yikes, it kept 'apple'!

 

You might have expected each call to start with an empty list, but instead the list “remembers” past calls. That’s because Python evaluates default arguments only once, when the function is defined, not each time it’s called. So the same list is reused. In other words, “default argument values are defined only once when the function is defined”, so using a mutable default is shared across calls. A safer pattern is to use None and then create a new list inside:

def add_item(item, my_list=None):
    if my_list is None:
        my_list = []
    my_list.append(item)
    return my_list

 

Now each call without an explicit list gets a fresh list. Being aware of this quirk saves a lot of confusion!

 

6. IDE and Editor Debuggers

 
Modern code editors like VS Code, PyCharm, or Spyder have visual debuggers built in. These let you set breakpoints with a click and run the code in “debug” mode. When execution hits a breakpoint, the IDE shows you all local variables, the call stack, and lets you step through code line by line or into functions. This is often easier for beginners than the command-line pdb prompt. For example, you can click a margin next to a line to pause there. You then hover over variables to see values, or open a “Variables” pane. PyCharm and VS Code can also watch expressions and highlight the current line as you step. Using an IDE debugger prevents the need to litter code with print statements. A typical mistake is not realizing you can do this; instead, many students copy code into Python IDLE or run line by line manually. Try setting breakpoints where you suspect the bug: the IDE will stop there, and you can inspect everything. If you haven’t used it, spending a little time learning your IDE’s debugger will pay off.

 

7. Rubber Duck (and Other Thinking Techniques)

 
Finally, sometimes the best “debugger” is none other than you (or a rubber duck). This old trick – known as rubber duck debugging – means explaining your code, line by line, either to another person or even to an inanimate object. Verbally articulating what your code should do often reveals contradictions or mistakes. Beginners frequently discover their own bugs while just reading code aloud or walking through it on paper. Try explaining your logic in simple terms: e.g. “First I check this, then I update that… wait, why am I doing it twice?” You might suddenly realize you meant to do something different. Pair programming or asking a friend to listen can also help; sometimes just framing the problem out loud points you to the solution. This isn’t a technical tool with commands, but a mindset: step back, take a deep breath, and reason through your code. Often the bug jumps out when you slow down and clarify each step.

 

Wrapping Up

 
Each of these techniques can expose different classes of bugs. In practice, a mix of them usually works best: start by reading the error message, add print/log statements or assertions to pinpoint where things diverge from expectations, and use an interactive debugger or IDE when you need to inspect complex state. Remember, debugging is a normal part of coding – even experts do it! Remember that every bug you fix is a lesson learned.
 
 

Kanwal Mehreen Kanwal is a machine learning engineer and a technical writer with a profound passion for data science and the intersection of AI with medicine. She co-authored the ebook “Maximizing Productivity with ChatGPT”. As a Google Generation Scholar 2022 for APAC, she champions diversity and academic excellence. She’s also recognized as a Teradata Diversity in Tech Scholar, Mitacs Globalink Research Scholar, and Harvard WeCode Scholar. Kanwal is an ardent advocate for change, having founded FEMCodes to empower women in STEM fields.

Recent Articles

Related Stories

Leave A Reply

Please enter your comment!
Please enter your name here