Debugging & Profiling

15 questions found

How do you use the built-in pdb debugger to pause execution at a specific line?

Beginner
Insert 'import pdb; pdb.set_trace()' (or the shorter 'breakpoint()' in Python 3.7+) at the line where you want execution to pause — this drops you into an interactive debugger prompt where you can inspect variables, step through code, and evaluate expressions.
def calculate_total(items):
    breakpoint()  # execution pauses here, drops into pdb
    return sum(item.price for item in items)
Real-world example Pausing mid-function to inspect why a calculated total doesn't match the expected value.

Common follow-ups: What are the basic pdb commands for stepping through code: 'n', 's', 'c', and 'l'?

Exception Handling

How do you use print statements effectively for debugging, including showing variable names alongside values?

Beginner
Use an f-string with the '=' specifier (Python 3.8+) to automatically print both the variable's NAME and its value in one concise expression, avoiding the tedium of manually typing 'print(f"variable_name: {variable_name}")'.
count = 42
name = "Sam"
print(f"{count=}, {name=}")  # "count=42, name='Sam'" -- auto-includes the variable name
Real-world example Quickly checking a variable's current value and type during ad-hoc debugging without writing a full debug statement.

Common follow-ups: Why is print-based debugging generally considered less efficient than using a proper debugger for complex bugs?

Functions & Scope

How do you use the traceback module to capture and format a full exception traceback as a string, useful for logging?

Intermediate
traceback.format_exc() returns the current exception's full traceback (including file names, line numbers, and the exception message) as a formatted string, letting you log or store detailed error diagnostics rather than just the bare exception message.
import traceback

try:
    1 / 0
except ZeroDivisionError:
    error_details = traceback.format_exc()
    logging.error(f"Operation failed:\n{error_details}")
Real-world example Logging a complete, detailed stack trace to a file or monitoring service when an unexpected exception occurs in production.

Common follow-ups: What's the difference between traceback.format_exc() and traceback.print_exc()?

Logging

How do you use the timeit module to accurately measure a small code snippet's execution time?

Intermediate
timeit.timeit(stmt, number=N) runs the given code snippet N times (default 1,000,000) and returns the TOTAL time taken, automatically minimizing common measurement pitfalls (like garbage collection interference) that a naive time.time() before/after comparison wouldn't account for.
import timeit

time_taken = timeit.timeit("'-'.join(str(n) for n in range(100))", number=10000)
print(f"Total: {time_taken:.4f}s")

# Comparing two approaches
print(timeit.timeit('"a" + "b"', number=1_000_000))
print(timeit.timeit('"".join(["a", "b"])', number=1_000_000))
Real-world example Comparing the actual performance of two different implementations of a small, frequently-called function.

Common follow-ups: Why does timeit disable automatic garbage collection during its measurement by default?

Debugging & Profiling

How do you use cProfile to identify which functions are consuming the most time in a larger program?

Intermediate
Run your program (or a specific function call) through cProfile.run() or the command line (`python -m cProfile script.py`), which produces a detailed report showing call counts, cumulative time, and per-call time for every function invoked — pinpointing exactly where performance bottlenecks actually are, rather than guessing.
import cProfile

def slow_function():
    return sum(i * i for i in range(1_000_000))

cProfile.run("slow_function()")
# Prints a table: ncalls, tottime, percall, cumtime, percall, filename:lineno(function)
Real-world example Identifying the actual performance bottleneck in a slow script before deciding what to optimize, rather than guessing.

Common follow-ups: What does the 'cumtime' column specifically measure, versus 'tottime', in cProfile's output?

Debugging & Profiling

How do assert statements work for debugging, and why should they NOT be relied upon for production input validation?

Intermediate
'assert condition, message' raises an AssertionError with the given message if the condition is falsy — useful for catching PROGRAMMER errors and internal invariant violations during development, but assertions are STRIPPED ENTIRELY when Python runs with the -O (optimize) flag, so they must never be used to validate untrusted external input in production code.
def calculate_discount(price, percent):
    assert 0 <= percent <= 100, f"Invalid percent: {percent}"  # catches a programmer bug during dev
    return price * (1 - percent / 100)

# python -O script.py  -- this assert would be silently REMOVED, no validation at all!
Real-world example Catching an internal logic bug (like an invariant violation) during development and testing, but NOT for validating user input.

Common follow-ups: What SHOULD you use instead of assert to validate untrusted external input reliably in production?

Exception Handling

How would you use the 'faulthandler' module to diagnose a program that crashes with a segmentation fault, typically from a C extension?

Advanced
faulthandler.enable() registers handlers for fatal errors (like SIGSEGV) that dump a Python-level traceback to stderr before the process crashes — invaluable for diagnosing crashes originating in C extensions (like NumPy or a native library) that would otherwise crash silently with no useful Python-level diagnostic information.
import faulthandler
faulthandler.enable()

# If a C extension later causes a segfault, faulthandler prints a Python
# traceback showing exactly where in your Python code the crash occurred,
# instead of just silently terminating the process
Real-world example Diagnosing a mysterious crash in a data-processing script caused by a bug in a native C extension dependency.

Common follow-ups: How would you enable faulthandler automatically for EVERY Python process, without modifying your script's code?

Debugging & Profiling

How do you use memory_profiler (or tracemalloc from the standard library) to identify a memory leak's source in a Python application?

Advanced
tracemalloc.start() begins tracking memory allocations by source location; taking two snapshots (tracemalloc.take_snapshot()) before and after a suspected leaking operation, then comparing them with snapshot2.compare_to(snapshot1, 'lineno'), reveals exactly which lines of code are responsible for growing memory usage.
import tracemalloc

tracemalloc.start()
snapshot1 = tracemalloc.take_snapshot()
run_suspected_leaking_operation()
snapshot2 = tracemalloc.take_snapshot()

top_stats = snapshot2.compare_to(snapshot1, "lineno")
for stat in top_stats[:5]:
    print(stat)
Real-world example Pinpointing the exact line of code responsible for a service's steadily growing memory usage over time.

Common follow-ups: How does tracemalloc's overhead compare to running a full external memory profiler, in terms of production suitability?

Memory Management & Garbage Collection

How would you use the 'logging' module's exception logging (logger.exception()) INSIDE an except block to automatically capture the full traceback?

Advanced
logger.exception(message) is a convenience method equivalent to logger.error(message, exc_info=True) — it MUST be called from WITHIN an except block (where sys.exc_info() has current exception data), and automatically appends the full traceback to the log record, avoiding the need to manually call traceback.format_exc().
import logging
logger = logging.getLogger(__name__)

try:
    result = 1 / 0
except ZeroDivisionError:
    logger.exception("Failed to calculate result")  # automatically includes the full traceback
Real-world example Automatically logging complete diagnostic tracebacks for unexpected errors caught in a production service's exception handlers.

Common follow-ups: What happens if you accidentally call logger.exception() OUTSIDE of an active except block?

Logging

How do you use pdb's post-mortem debugging mode to inspect a program's state exactly at the point where an unhandled exception occurred?

Advanced
pdb.post_mortem() (or running a script with `python -m pdb script.py` and letting it crash) drops you into the debugger at the EXACT frame where an exception was raised, letting you inspect local variables and the call stack as they existed at the moment of failure, rather than needing to reproduce the bug with a manually-placed breakpoint.
import pdb

try:
    risky_operation()
except Exception:
    pdb.post_mortem()  # drops into the debugger AT the point of failure, with full context
Real-world example Investigating an unexpected crash's exact state (local variables, call stack) without needing to reproduce it with a manually inserted breakpoint.

Common follow-ups: How does 'python -m pdb -c continue script.py' automate reaching post-mortem debugging for an uncaught exception?

Exception Handling

Showing 1–10 of 15