Latticework

Command Palette

Search for a command to run...

Debugging

Debugging Tooling

16 min

Explanation

Beyond reading tracebacks, three tools cover most real debugging: print debugging (fast, universal, but requires guessing in advance what's worth printing), assertions (cheap sanity checks that fail LOUDLY and immediately, right where an assumption breaks), and interactive debuggers (like Python's pdb, or your editor's breakpoint UI — pause execution and inspect EVERYTHING at that moment).

def process(data):
    assert len(data) > 0, "process() called with empty data"
    return data[0] * 2

An assertion failing tells you EXACTLY which assumption was violated, immediately, at the exact line — much faster than tracing a confusing downstream error back to its actual root cause several function calls later.

Try it

Without the assertion, calculate_average([]) would raise a confusing ZeroDivisionError deep inside the function -- the assertion catches the REAL problem (empty input) at the door, with a message that explains what actually went wrong.

Loading editor…
Explanation

A minimal manual call tracer is the same idea behind print-debugging, formalized slightly:

def trace_calls(func, *args):
    result = func(*args)
    return result, f"called with args: {args}"

result, log = trace_calls(lambda x, y: x + y, 2, 3)
print(log)      # called with args: (2, 3)
print(result)   # 5

A real debugger does this automatically for EVERY function call, without you writing any wrapper — that's the core tradeoff: print/manual tracing needs no setup and works anywhere, but only shows what you thought in advance to log; a debugger needs a bit more setup but lets you inspect anything, on demand, after the fact.

Exercise

Write `trace_calls(func, *args)`: call `func(*args)`, and return a tuple `(result, "called with args: {args}")` — a minimal manual version of what a debugger's call tracer does.

Exercise

Write `assert_invariant(condition, message)`: raise `AssertionError(message)` if `condition` is False; otherwise return True.

Quiz

What's a key advantage of using an interactive debugger (breakpoints, step-through) over scattering print() statements?

Checkpoint

You can use assertions to catch violated assumptions immediately and clearly, and understand the tradeoffs between print debugging, assertions, and interactive debuggers.