Python

P
PythonHub
Progress 0%

Python

  • Home
  • History of Python
  • Applications of Python
  • Introduction To Python
    • What is Python
    • Character Set
    • Tokens in Python
    • Python Execution Mode
    • Variable And Identifiers
    • Data Types in Python
    • Operators And Expressions
    • Constants in Python
    • Assignment Statement
    • Input / Output in Python
    • Simple Python Scripts
    • Namespace in Python
    • 📝 Assignments
  • Operators in Python
    • Arithmetic Operators
    • Assignment Operators
    • Shorthand Assignment Operators
    • Relational Operators
    • Logical Operators
    • Bitwise Operators
    • Special Operators
    • 📝 Assignments
  • Input Output in Python
    • Accept Input
    • Output Formatting
    • 📝 Assignments
  • Conditional Statement
    • Decision Making
    • if Statement
    • IF-ELSE STATEMENT
    • IF-ELSE LADDER
    • NESTED IF-ELSE
    • Short Hand IF-ELSE
    • 📝 Assignments
  • Loops
    • Introduction to Loops
    • While Loop
    • Nested While Loop
    • 📝 While Loop Assignments
    • For Loop
    • For Loop Examples
    • Nested For Loop
    • Nested For Loop Examples
    • Infinite While Loops
    • Infinite For Loops
    • Break, Continue and Else in Loops
    • Difference Between For and While Loop
    • For Each Loop
    • 📝 For Each Assignments
    • 📝 All Loops Assignments
  • List
    • List in Python
    • Access List Elements
    • List Functions
    • Iterate (Loop) List
    • List Comprehension
    • 📝 Assignments
  • Tuple
    • Tuple in Python
    • Access Tuple Elements
    • Tuple Functions
    • Iterate (Loop) Tuple
    • Unpack Tuple
    • Tuple Comprehension
    • 📝 Assignments
  • Set
    • Set in Python
    • Access Set Elements
    • Set Methods
    • Iterate (Loop) Set
    • Pack/Unpack Set
    • Set Comprehension
    • 📝 Assignments
  • Dictionary
    • Dictionary
    • Access Dictionary Items
    • Dictionary Methods
    • Iterate (Loop) Dictionary
    • Formatting Dictionaries
    • Nested Dictionaries
    • Dictionary Comprehension
    • 📝 Assignments
  • Diff List Tuple Set Dictionary
    • List vs Tuple
    • List vs Set
    • List vs Dictionary
    • Tuple vs Set
    • Tuple vs Dictionary
    • Dictionary vs Set
    • 📝 Assignments
  • Exception
    • Error vs Exception
    • Types of Exception
    • Exception Handling
    • User Defined Exception
    • Logging Exception
    • 📝 Assignments
  • Functions
    • Introduction to Functions
    • Modular Programming
    • Types of Functions
    • Inbuilt Functions
    • Need For User-Defined Function
    • Elements of User Defined Function
    • Function Arguments
    • Nesting of Functions
    • Recursion
    • Global Local and Non Local
    • Python Lambda Functions
    • 📝 Assignments
  • Python Module
    • Introduction to Module
    • Inbuilt Modules in Python
    • User Defined Module
    • 📝 Assignments
  • File Handling
    • Introduction to Files
    • Create File
    • Read Files
    • Write to File
    • Rename File
    • Copy File
    • Move File
    • List Files in Directory
    • Binary Files
    • Zipping and Unzipping Files
    • 📝 Assignments
  • Strings
    • Basics of Strings
    • String Special Operators
    • String Formatting Operators
    • String Methods
    • 📝 Assignments
  • Regular Expressions
  • Python OOPS
    • Basics of Object Oriented
    • What are Classes and Objects?
    • Creating Class and Object
    • OOP vs Procedural Programming
    • Difference Between Classes and Objects
    • Constructors
    • Destructor
    • Built Class Methods and Attributes
    • Class and Instance Variables
    • Inheritance in Python
    • Single Inheritance
    • Multiple Inheritance
    • Multilevel Inheritance
    • Hierarchical Inheritance
    • Hybrid Inheritance
    • Abstraction
    • Method Overriding
    • Abstract Method
    • Interfaces in Python
    • Abstract Class vs Interface
    • Public, Private and Protected
    • Overloading vs Overriding
    • Inheritance vs Composition
    • Encapsulation
    • Polymorphism
    • Inner Classes
    • 📝 Assignments
  • Advanced Python
    • Iterator in Python
    • Generator in Python
    • Decorator in Python
    • Type Hints in Python
    • Async/Await Programming
    • Dataclasses in Python
    • Context Managers in Python
    • Property Decorator in Python
    • __slots__ in Python
    • Enums in Python
    • Walrus Operator in Python
    • Match-Case in Python
    • 📝 Assignments
  • Python Standard Library
    • Collections Module
    • Itertools Module
    • Functools Module
    • Datetime Module
    • JSON Module
    • OS Module
    • Sys Module
    • Random Module
    • Math Module
    • 📝 Assignments
  • Python Testing
    • Unit Testing in Python
    • Pytest Framework
    • Mocking in Python
    • 📝 Assignments
  • Python Best Practices
    • PEP 8 Style Guide
    • Docstrings in Python
    • Logging in Python
    • Code Optimization Tips
    • Debugging Techniques
    • 📝 Assignments
  • MySQL Database in Python
    • Introduction to MySQL with Python
    • DBMS vs File System
    • Connecting to MySQL Database
    • Create Database in MySQL
    • Create Table in MySQL
    • Insert Data in MySQL
    • Insert Multiple Rows
    • Select Data from MySQL
    • WHERE Clause in MySQL
    • Update Data in MySQL
    • Delete Data from MySQL
    • Parameterized Queries
    • Transaction Management
    • Error Handling
    • Connection Pooling
    • MySQL Drivers Guide
    • Joins in MySQL
    • Aggregation Functions
    • Backup MySQL Database
    • Best Practices
    • 📝 Assignments
  • MySQL Database Operations
    • SELECT Statement
    • MySQL Operators
    • DDL Statements
    • DML Statements
    • Subqueries
    • JOIN Operations
    • Aggregation
    • Case Study
    • 📝 Assignments
  • Graphics in Python
  • Threads in Python
    • Introduction to Threads
    • Process vs Threads
    • Concurrent Programming & GIL
    • Uses of Threads
    • Creating Threads
    • Single Tasking
    • Multi Tasking
    • Thread Synchronization
    • 📝 Assignments
  • Interview Questions & Answers
  • Python Case Studies
  • Multiple Choice Questions
  • 📝 Practice Problems
Get in Touch
  • tech2dsm@gmail.com

© Sankalan Data Tech

Python Language Interactive Tutorial

Python: Debugging

Python Debugging - Complete Guide

Find and fix bugs faster with these debugging techniques.

Created by Sankalan Data Tech Team Verified
Data Engineers, Analysts, Scientists & Trainers
Created by experienced Python developers, data engineers, and data scientists to make programming easy through practical examples, real-world experience, and clear explanations.
On this page:
  • What is Debugging?
  • Print Debugging
  • Python Debugger (pdb)
  • Debugging with Logging
  • Common Bugs and How to Fix Them
  • Real-World Debugging
  • Best Practices
  • Try It Yourself
  • Quick Quiz
  • Frequently Asked Questions
Share this tutorial:
Twitter LinkedIn Facebook WhatsApp Reddit Telegram Email Copy Link
What You'll Learn Here
  • What is debugging — finding and fixing bugs in your code
  • Print debugging — the simplest way to debug
  • pdb — the Python debugger
  • Logging for debugging — using logs to find bugs
  • Common bugs — and how to fix them
  • Real-world debugging — practical techniques

What is Debugging?

Debugging is the process of finding and fixing bugs (errors) in your code. It's a skill every programmer needs to learn.

Think of debugging like detective work. You have a crime (the bug), you collect clues (error messages, logs), you interview witnesses (run the code), and you figure out what happened and why.

The good news is that debugging gets easier with practice. You'll learn to recognize common patterns and develop a systematic approach to finding bugs.

💡 Key concept: Debugging is a skill that takes practice. The more you do it, the better you get at finding and fixing bugs.

Print Debugging

1

The Simplest Debugging Technique

Print debugging is the most common debugging technique. You add print statements to your code to see what's happening at different points.

# Print Debugging

print("=" * 50)
print("PRINT DEBUGGING")
print("=" * 50)

# ============================================================
# WITHOUT PRINT DEBUGGING - Hard to find the bug
# ============================================================

print("\n1. WITHOUT PRINT DEBUGGING")

def calculate_average(numbers):
    total = 0
    for num in numbers:
        total += num
    return total / len(numbers)

# This function seems correct, but...
numbers = [10, 20, 30, 40, 50]
avg = calculate_average(numbers)
print(f"   Average: {avg}")  # Works fine

# But what if something is wrong?
numbers = []
try:
    avg = calculate_average(numbers)
except ZeroDivisionError as e:
    print(f"    Error: {e}")
# We can't see what's happening inside the function


# ============================================================
# WITH PRINT DEBUGGING - Easy to see what's happening
# ============================================================

print("\n2. WITH PRINT DEBUGGING")

def calculate_average_with_print(numbers):
    print(f"    Inside calculate_average with numbers: {numbers}")
    total = 0
    print(f"    Starting total: {total}")
    
    for i, num in enumerate(numbers):
        print(f"    Iteration {i}: num = {num}")
        total += num
        print(f"    New total: {total}")
    
    print(f"    Final total: {total}")
    print(f"    Length: {len(numbers)}")
    
    if len(numbers) == 0:
        print("    Empty list detected!")
        return 0
    
    result = total / len(numbers)
    print(f"    Average: {result}")
    return result

print("   Testing with empty list:")
avg = calculate_average_with_print([])
print(f"   Result: {avg}")

print("\n   Testing with numbers:")
avg = calculate_average_with_print([10, 20, 30])
print(f"   Result: {avg}")


# ============================================================
# PRINT DEBUGGING TIPS
# ============================================================

print("\n3. PRINT DEBUGGING TIPS")

print("""
┌─────────────────────────────────────────────────────────────────────┐
│ PRINT DEBUGGING TIPS                                               │
├─────────────────────────────────────────────────────────────────────┤
│ 1. Print variable values at key points                            │
│ 2. Use descriptive messages: print(f"User ID: {user_id}")         │
│ 3. Print before and after operations                              │
│ 4. Print the type of variables: print(type(variable))             │
│ 5. Print function arguments when entering a function              │
│ 6. Add a marker: print("=== ENTERING LOOP ===")                  │
│ 7. Print loop iterations: print(f"i={i}, value={value}")         │
│ 8. Remember to remove print statements after debugging            │
└─────────────────────────────────────────────────────────────────────┘
""")

Print debugging key points:

  • Add print statements — to see what's happening
  • Print variable values — at key points
  • Use markers — to identify different parts
  • Remove later — clean up after debugging

Quick Check: What is the simplest debugging technique? (Answer: Print debugging — adding print statements)

Python Debugger (pdb)

2

Interactive Debugging with pdb

pdb (Python Debugger) is an interactive debugger that lets you step through your code line by line, inspect variables, and control the execution flow.

# Python Debugger (pdb)

print("=" * 50)
print("PYTHON DEBUGGER (pdb)")
print("=" * 50)

# ============================================================
# USING pdb - Set breakpoints
# ============================================================

print("\n1. USING pdb")

print("""
import pdb

def calculate_average(numbers):
    pdb.set_trace()  # Start debugger here
    total = 0
    for num in numbers:
        total += num
    return total / len(numbers)

# When you run this, the debugger will start at set_trace()
# and you can step through the code
""")


# ============================================================
# PDB COMMANDS
# ============================================================

print("\n2. PDB COMMANDS")

print("""
┌─────────────────────┬─────────────────────────────────────────────────────┐
│ Command             │ What it does                                      │
├─────────────────────┼─────────────────────────────────────────────────────┤
│ n (next)            │ Execute the next line (step over)                 │
│ s (step)            │ Step into a function call                        │
│ c (continue)        │ Continue execution until next breakpoint          │
│ p (print)           │ Print a variable value: p variable                │
│ pp (pretty print)   │ Pretty print a variable                          │
│ l (list)            │ Show source code around current line              │
│ w (where)           │ Show current stack trace                          │
│ u (up)              │ Move up one frame in the stack                   │
│ d (down)            │ Move down one frame in the stack                 │
│ q (quit)            │ Quit the debugger                                │
│ h (help)            │ Show help                                        │
│ !                   │ Execute a Python statement                       │
└─────────────────────┴─────────────────────────────────────────────────────┘
""")


# ============================================================
# BREAKPOINT() - Python 3.7+ (No import needed)
# ============================================================

print("\n3. breakpoint() - Python 3.7+")

print("""
def process_data(data):
    total = 0
    for item in data:
        breakpoint()  # Start debugger here
        total += item
    return total

# Just call breakpoint() where you want to start debugging
# No need to import pdb!
""")

# ============================================================
# EXAMPLE USAGE
# ============================================================

print("\n4. EXAMPLE USAGE")

print("""
def debug_example():
    x = 10
    y = 20
    breakpoint()  # Debugger starts here
    z = x + y
    return z

# When you run this:
# > (1)debug_example()
# -> z = x + y
# (Pdb) p x
# 10
# (Pdb) p y
# 20
# (Pdb) n
# > (1)debug_example()
# -> return z
# (Pdb) p z
# 30
# (Pdb) c
# 30
""")


# ============================================================
# RUNNING FROM COMMAND LINE
# ============================================================

print("\n5. RUNNING FROM COMMAND LINE")

print("""
# Run a script with the debugger from the start
python -m pdb my_script.py

# This starts pdb before the script runs
# You can set breakpoints and step through the code

# Set a breakpoint in the code
import pdb
pdb.set_trace()  # Or breakpoint() in Python 3.7+
""")

pdb key points:

  • pdb.set_trace() — start debugger
  • breakpoint() — Python 3.7+ shortcut
  • n (next) — step to next line
  • p (print) — print variable values
  • c (continue) — continue execution

Quick Check: What command prints a variable in pdb? (Answer: p variable_name)

Debugging with Logging

3

Using Logs for Debugging

Logging is a great way to debug, especially in production. It gives you a permanent record of what happened.

# Debugging with Logging

import logging
import time

print("=" * 50)
print("DEBUGGING WITH LOGGING")
print("=" * 50)

# ============================================================
# SETUP LOGGING FOR DEBUGGING
# ============================================================

print("\n1. SETUP LOGGING")

# Configure logging
logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(levelname)s - %(message)s'
)

logger = logging.getLogger(__name__)

# ============================================================
# FUNCTION WITH LOGGING
# ============================================================

print("\n2. FUNCTION WITH LOGGING")

def process_data_with_logging(data):
    logger.debug(f"Processing data: {data}")
    
    if not data:
        logger.warning("Empty data received")
        return []
    
    result = []
    for i, item in enumerate(data):
        logger.debug(f"Processing item {i}: {item}")
        try:
            processed = item * 2
            result.append(processed)
            logger.debug(f"Item {i} processed: {processed}")
        except Exception as e:
            logger.error(f"Error processing item {i}: {e}", exc_info=True)
    
    logger.info(f"Processed {len(result)} items out of {len(data)}")
    return result

# Test with good data
print("\n   Testing with good data:")
result = process_data_with_logging([1, 2, 3, 4, 5])
print(f"   Result: {result}")

# Test with bad data
print("\n   Testing with bad data:")
result = process_data_with_logging([1, "two", 3, None, 5])
print(f"   Result: {result}")

# ============================================================
# LOG LEVELS FOR DEBUGGING
# ============================================================

print("\n3. LOG LEVELS FOR DEBUGGING")

print("""
┌─────────────┬─────────────────────────────────────────────────────┐
│ Level       │ When to use for debugging                         │
├─────────────┼─────────────────────────────────────────────────────┤
│ DEBUG       │ Detailed information for debugging                │
│ INFO        │ Confirmation that things are working              │
│ WARNING     │ Something unexpected but not an error             │
│ ERROR       │ A problem occurred                                │
│ CRITICAL    │ A serious error                                   │
└─────────────┴─────────────────────────────────────────────────────┘

Set level to DEBUG to see everything:
    logging.basicConfig(level=logging.DEBUG)

In production, set to WARNING or ERROR:
    logging.basicConfig(level=logging.WARNING)
""")

# ============================================================
# LOGGING VS PRINT
# ============================================================

print("\n4. LOGGING VS PRINT")

print("""
Logging advantages over print:
    ✅ Timestamps automatically added
    ✅ Different log levels (DEBUG, INFO, WARNING, ERROR)
    ✅ Can be disabled without removing code
    ✅ Can write to files
    ✅ Can be configured for different environments
    ✅ Thread-safe
    ✅ Includes context (module, function, line number)

When to use:
    • Print: Quick local debugging
    • Logging: Production debugging, permanent records
""")

Logging for debugging key points:

  • Use DEBUG level — for detailed debugging info
  • Log function entries — know when functions are called
  • Log variable values — see what's being processed
  • Log errors — with traceback for exceptions
  • Better than print — timestamps, levels, files

Quick Check: What log level should you use for debugging? (Answer: DEBUG)

Common Bugs and How to Fix Them

4

Fixing Common Python Bugs

Here are some of the most common bugs in Python and how to fix them.

# Common Bugs and How to Fix Them

print("=" * 50)
print("COMMON BUGS AND HOW TO FIX THEM")
print("=" * 50)

# ============================================================
# BUG 1: IndexError - List index out of range
# ============================================================

print("\n1. IndexError - List index out of range")

# Buggy code
my_list = [1, 2, 3]
try:
    print(f"   my_list[3]: {my_list[3]}")  # IndexError
except IndexError as e:
    print(f"    Error: {e}")

# Fix: Check length before accessing
def safe_get(lst, index, default=None):
    if 0 <= index < len(lst):
        return lst[index]
    return default

print(f"   safe_get(my_list, 3): {safe_get(my_list, 3)}")


# ============================================================
# BUG 2: KeyError - Dictionary key not found
# ============================================================

print("\n2. KeyError - Dictionary key not found")

# Buggy code
my_dict = {"name": "Alice"}
try:
    print(f"   my_dict['age']: {my_dict['age']}")  # KeyError
except KeyError as e:
    print(f"    Error: {e}")

# Fix: Use get() method
print(f"   my_dict.get('age', 'Unknown'): {my_dict.get('age', 'Unknown')}")


# ============================================================
# BUG 3: TypeError - Operation with wrong type
# ============================================================

print("\n3. TypeError - Operation with wrong type")

# Buggy code
try:
    result = "5" + 3  # TypeError
except TypeError as e:
    print(f"    Error: {e}")

# Fix: Convert types properly
print(f"   int('5') + 3: {int('5') + 3}")
print(f"   '5' + str(3): {'5' + str(3)}")


# ============================================================
# BUG 4: ValueError - Invalid value for operation
# ============================================================

print("\n4. ValueError - Invalid value")

# Buggy code
try:
    num = int("abc")  # ValueError
except ValueError as e:
    print(f"    Error: {e}")

# Fix: Validate input
def safe_int(value):
    try:
        return int(value)
    except ValueError:
        return None

print(f"   safe_int('abc'): {safe_int('abc')}")
print(f"   safe_int('123'): {safe_int('123')}")


# ============================================================
# BUG 5: AttributeError - Object has no attribute
# ============================================================

print("\n5. AttributeError - Object has no attribute")

# Buggy code
class Person:
    def __init__(self, name):
        self.name = name

p = Person("Alice")
try:
    print(f"   p.age: {p.age}")  # AttributeError
except AttributeError as e:
    print(f"    Error: {e}")

# Fix: Check if attribute exists
print(f"   hasattr(p, 'age'): {hasattr(p, 'age')}")
print(f"   getattr(p, 'age', 'Unknown'): {getattr(p, 'age', 'Unknown')}")


# ============================================================
# BUG 6: ZeroDivisionError - Division by zero
# ============================================================

print("\n6. ZeroDivisionError - Division by zero")

# Buggy code
try:
    result = 10 / 0  # ZeroDivisionError
except ZeroDivisionError as e:
    print(f"    Error: {e}")

# Fix: Check before dividing
def safe_divide(a, b):
    if b == 0:
        return None
    return a / b

print(f"   safe_divide(10, 0): {safe_divide(10, 0)}")


# ============================================================
# BUG 7: IndentationError - Wrong indentation
# ============================================================

print("\n7. IndentationError - Wrong indentation")

print("""
#  Wrong indentation
def buggy_function():
print("This is wrong")  # IndentationError

#  Correct indentation
def good_function():
    print("This is correct")
""")


# ============================================================
# BUG 8: NameError - Variable not defined
# ============================================================

print("\n8. NameError - Variable not defined")

# Buggy code
try:
    print(f"   undefined_variable: {undefined_variable}")  # NameError
except NameError as e:
    print(f"    Error: {e}")

# Fix: Define the variable
defined_variable = "I'm defined"
print(f"   defined_variable: {defined_variable}")

Common bugs key points:

  • IndexError — check list bounds
  • KeyError — use dict.get()
  • TypeError — convert types properly
  • ValueError — validate input
  • AttributeError — check with hasattr()

Quick Check: How do you safely access a dictionary key? (Answer: Use dict.get(key, default))

Real-World Debugging

5

Debugging a Real Application

# Real-World Debugging Example

print("=" * 60)
print("REAL-WORLD DEBUGGING")
print("=" * 60)

# ============================================================
# BUGGY APPLICATION - A simple user manager
# ============================================================

import logging
import pdb

# Set up logging for debugging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

class UserManager:
    """Manage users with a bug"""
    
    def __init__(self):
        self.users = []
        self.user_counter = 0
    
    def add_user(self, name, email):
        """Add a user - has a bug!"""
        # BUG: Missing check for email format
        # BUG: Missing check for duplicate user
        
        self.user_counter += 1
        user = {
            "id": self.user_counter,
            "name": name,
            "email": email,
            "active": True
        }
        self.users.append(user)
        logger.debug(f"Added user: {user}")
        return user
    
    def get_user(self, user_id):
        """Get a user by ID - has a bug!"""
        # BUG: Returns None if user not found
        # Should raise an exception or handle properly
        
        for user in self.users:
            if user["id"] == user_id:
                return user
        return None
    
    def delete_user(self, user_id):
        """Delete a user - has a bug!"""
        # BUG: Removes user but doesn't handle case where user not found
        # BUG: Doesn't check if user is active before deleting
        
        for i, user in enumerate(self.users):
            if user["id"] == user_id:
                self.users.pop(i)
                logger.debug(f"Deleted user {user_id}")
                return True
        return False
    
    def get_active_users(self):
        """Get all active users - has a bug!"""
        # BUG: Returns None if no active users, should return empty list
        active = [user for user in self.users if user["active"]]
        if not active:
            return None  # BUG: Should return []
        return active


# ============================================================
# DEBUGGING PROCESS - Step by Step
# ============================================================

print("\n1. IDENTIFYING THE BUG")

print("""
Problem: The application crashes when trying to get active users
when there are no active users.

Let's add print statements to understand what's happening.
""")

# Create the manager
manager = UserManager()

# Add some users
manager.add_user("Alice", "alice@example.com")
manager.add_user("Bob", "bob@example.com")

print("   Initial users:", manager.users)

# Test get_active_users - should return all users
print("\n2. TEST GET_ACTIVE_USERS")

print("   Before fix - get_active_users():")
result = manager.get_active_users()
print(f"   Result: {result}")  # Returns a list of users

# Test when no users - this is where the bug appears
print("\n3. TEST WITH NO USERS")

# Create a new manager with no users
empty_manager = UserManager()
print("   New manager with no users")

print("   Before fix - get_active_users():")
try:
    result = empty_manager.get_active_users()
    print(f"   Result: {result}")  # This returns None
    print(f"   Type: {type(result)}")
except Exception as e:
    print(f"    Error: {e}")

print("\n   The bug: get_active_users returns None instead of []")

# ============================================================
# FIXING THE BUG
# ============================================================

print("\n4. FIXING THE BUG")

# Fixed version
class FixedUserManager(UserManager):
    """Fixed version of UserManager"""
    
    def get_active_users(self):
        """Get all active users - fixed version"""
        active = [user for user in self.users if user["active"]]
        return active  # Always return a list, even if empty

# Test the fix
print("   Testing the fix:")
fixed_manager = FixedUserManager()
result = fixed_manager.get_active_users()
print(f"   get_active_users() on empty manager: {result}")
print(f"   Type: {type(result)}")
print(f"    Bug fixed! Returns [] instead of None")


# ============================================================
# DEBUGGING PROCESS SUMMARY
# ============================================================

print("\n5. DEBUGGING PROCESS SUMMARY")

print("""
1. Reproduce the bug - know how to make it happen
2. Add print/logging statements - see what's happening
3. Use the debugger (pdb) - step through the code
4. Find the root cause - understand why it's happening
5. Fix the bug - make the change
6. Test the fix - verify it works
7. Look for other bugs - test other cases
8. Remove debugging code - clean up
""")

Real-world debugging key points:

  • Reproduce the bug — know how to trigger it
  • Add logging — understand what's happening
  • Use the debugger — step through the code
  • Find the root cause — why is it happening?
  • Fix and test — verify the fix works

Quick Check: What's the first step in debugging? (Answer: Reproduce the bug)

Best Practices

6

Debugging Best Practices

# Debugging Best Practices

print("=" * 60)
print("DEBUGGING BEST PRACTICES")
print("=" * 60)

# ============================================================
# 1. READ THE ERROR MESSAGE
# ============================================================

print("\n1. READ THE ERROR MESSAGE")

print("""
# Error messages tell you:
# - What went wrong
# - Where it went wrong (line number)
# - What type of error it is

Example:
    Traceback (most recent call last):
      File "my_script.py", line 10, in 
        result = calculate(data)
      File "my_script.py", line 5, in calculate
        return total / len(numbers)
    ZeroDivisionError: division by zero

This tells us:
    • Error type: ZeroDivisionError
    • Location: line 5 in calculate function
    • Cause: division by zero
""")


# ============================================================
# 2. USE THE DEBUGGER
# ============================================================

print("\n2. USE THE DEBUGGER")

print("""
# Don't just use print statements
# Use the debugger to step through code

# Insert a breakpoint
breakpoint()  # Python 3.7+
# or
import pdb
pdb.set_trace()

# Then use commands:
#   n - next line
#   p variable - print variable
#   c - continue
""")


# ============================================================
# 3. WRITE TESTS
# ============================================================

print("\n3. WRITE TESTS")

print("""
# Tests help prevent bugs and catch them early

def test_calculate_average():
    assert calculate_average([1, 2, 3]) == 2
    assert calculate_average([]) == 0  # This will fail if bug exists

# Run tests regularly
# pytest test_my_code.py
""")


# ============================================================
# 4. USE LOGGING INSTEAD OF PRINT
# ============================================================

print("\n4. USE LOGGING INSTEAD OF PRINT")

print("""
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

# In production, turn off DEBUG logs
# In development, keep them on

logger.debug("Debug message - for development")
logger.info("Info message - for production")
logger.error("Error message - always show")
""")


# ============================================================
# 5. TAKE BREAKS
# ============================================================

print("\n5. TAKE BREAKS")

print("""
Sometimes the best debugging technique is to step away.
When you're stuck:
- Walk away for 5-10 minutes
- Come back with fresh eyes
- Explain the problem to someone else (rubber duck debugging)

The solution often becomes obvious when you look at it again.
""")

# ============================================================
# 6. SUMMARY
# ============================================================

print("\n" + "=" * 60)
print("DEBUGGING BEST PRACTICES SUMMARY")
print("=" * 60)
print("""
- Read the error message carefully
- Use the debugger (pdb)
- Write tests to catch bugs early
- Use logging instead of print
- Take breaks when stuck
- Reproduce the bug reliably
- Check your assumptions
- Use version control to track changes
- Look for the simplest explanation
- Document the fix
""")

Best practices summary:

  • Read error messages — they tell you what's wrong
  • Use the debugger — pdb is powerful
  • Write tests — prevent and catch bugs
  • Use logging — better than print
  • Take breaks — fresh eyes help

Quick Check: What should you do when you're stuck debugging? (Answer: Take a break, come back with fresh eyes)

Try It Yourself

Practice debugging in the editor below.

Loading Pyodide... 0%
Python Code Editor
==================================================
DEBUGGING - PRACTICE
==================================================

1. BUGGY CODE
Normal case: 30.0
Error: division by zero
Single element: 42.0

2. FIXED CODE
Normal case: 30.0
Empty list: 0
Single element: 42.0
🏆

You've Got It!

You now understand debugging in Python. You know how to use print debugging, the Python debugger (pdb), logging, and common debugging techniques.

Quick Quiz

Test what you've learned:

1. What is the simplest debugging technique?
2. What function starts the Python debugger in Python 3.7+?
3. What pdb command prints a variable?
4. How do you safely access a dictionary key?
5. What's the first step in debugging?

Frequently Asked Questions

What is debugging in Python? ▼

Debugging is the process of finding and fixing errors (bugs) in your code. It involves identifying the problem, understanding why it's happening, and making the necessary changes to fix it.

What's the difference between print debugging and using pdb? ▼

Print debugging adds print statements to see values at runtime. pdb is an interactive debugger that lets you step through code line by line, inspect variables, and control execution. pdb is more powerful and doesn't require modifying code.

How do I use breakpoint() in Python? ▼

In Python 3.7+, you can use breakpoint() to start the debugger. When the code hits this line, it pauses and enters the debugger. You can then use pdb commands to inspect and step through the code.

What are the most common Python errors? ▼

Common errors include: IndexError (list out of bounds), KeyError (missing dictionary key), TypeError (wrong type), ValueError (invalid value), AttributeError (missing attribute), and ZeroDivisionError (division by zero).

Should I use print or logging for debugging? ▼

Use print for quick, local debugging. Use logging for production code and when you need more control (levels, timestamps, file output). Logging is better for long-term debugging and monitoring.

How can I prevent bugs? ▼

Write tests for your code, use type hints, follow best practices, use version control, review code with others, and write clear, readable code. Testing is the most effective way to prevent bugs.

Where to Go From Here

Now that you understand debugging, check out these related topics:

Logging

Learn more about logging for debugging.

Learn More →

Exception Handling

Learn how to handle exceptions properly.

Learn More →

Testing Assignments

Practice your debugging and testing skills.

Learn More →
Interview Resources
  • Python Syntax & Variables Interview Questions
  • Top SQL Interview Questions & Answers
  • SQL Joins: Displaying Data from Multiple Tables FAQ
  • Python Lists and Dictionaries Interview Questions
  • Python OOP Interview Questions
  • SQL Set Operators Interview Questions
Previous: Code Optimization Next: Best Practices Assignments →