IPython
debugging
Python programming
step-by-step guide
code troubleshooting

Step-by-step debugging with IPython

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

IPython debugging gives a faster feedback loop than print-debugging because you can inspect and mutate runtime state interactively. The key is to follow a repeatable process: reproduce, breakpoint, inspect, verify assumptions, then patch. This approach turns debugging into a controlled investigation instead of guesswork.

Start from a Reproducible Failure

Debugger sessions are only useful if failure can be reproduced consistently. Build a minimal failing input before stepping through code.

For script-level debugging:

bash
python -m pdb app.py

Useful commands:

  • 'l show source lines.'
  • 'n execute next line.'
  • 's step into function.'
  • 'c continue to next breakpoint.'
  • 'p expr print expression.'
  • 'q quit.'

Insert Breakpoints with IPython

Use set_trace near the suspicious state transition.

python
1from IPython.core.debugger import set_trace
2
3
4def parse_ratio(a: int, b: int) -> float:
5    set_trace()
6    return a / b
7
8print(parse_ratio(10, 2))

At the breakpoint, inspect locals and evaluate expressions in context.

Post-Mortem Debugging

When program already crashed, inspect failure context without restarting from the beginning.

python
%xmode Verbose
%run broken_script.py
%debug

%debug opens stack at exception frame and is very effective for long-running tasks.

Structured Investigation Loop

Use a consistent loop:

  1. Reproduce with minimal input.
  2. Breakpoint before failure.
  3. Inspect local state and call stack.
  4. Validate invariants.
  5. Apply minimal fix.
  6. Add regression test.

Example invariant checks:

python
assert isinstance(record, dict)
assert "amount" in record
assert record["amount"] >= 0

Assertions make implicit assumptions explicit.

Debugging in Notebooks

Notebook state can hide bugs due to out-of-order execution. For reliable debugging:

  • Restart kernel.
  • Run cells in order to failure.
  • Add breakpoints and inspect.
  • Re-run from clean state after patch.

Skipping this discipline often causes stale-variable confusion.

Async Debugging Pattern

Async code can be debugged the same way, but execution includes await boundaries.

python
1import asyncio
2from IPython.core.debugger import set_trace
3
4async def fetch_value(x):
5    await asyncio.sleep(0.01)
6    return x * 2
7
8async def main():
9    set_trace()
10    value = await fetch_value(5)
11    print(value)
12
13asyncio.run(main())

Inspect task flow and awaited calls when control path appears non-linear.

Combine Debugging with Logs

Interactive debugging helps local diagnosis, while logs provide production context. Use both:

  • Logs for historical request trace and identifiers.
  • Breakpoints for precise runtime inspection.

When fixing incidents, correlate debugger findings with real logs to avoid local-only assumptions.

Turn Findings into Guardrails

After fixing bug:

  • Remove temporary breakpoints.
  • Add regression test.
  • Document root cause briefly in issue tracker.
  • Keep minimal reproducible example.

This converts debugging effort into long-term reliability.

Useful Interactive Commands During Breakpoints

Inside an IPython breakpoint, a few commands consistently save time:

  • 'where to print stack frames.'
  • 'up and down to move through call frames.'
  • 'pp var for readable pretty-print output.'
  • '!command to run a quick shell command without leaving the session.'
python
1from IPython.core.debugger import set_trace
2
3def normalize(values):
4    set_trace()
5    return [v / max(values) for v in values]

During that pause, inspect values, evaluate max(values), and verify assumptions before changing code.

Common Pitfalls

  • Debugging without deterministic reproducer.
  • Changing many things before confirming root cause.
  • Forgetting to remove breakpoints before commit.
  • Trusting notebook state not rebuilt from fresh kernel.
  • Fixing symptom only without adding regression coverage.

Summary

  • IPython debugging is most effective with reproducible failures.
  • Use breakpoints for live state and %debug for post-mortem crashes.
  • Follow a structured investigation loop to reduce trial-and-error.
  • Pair interactive debugging with logs for complete diagnosis.
  • Always convert confirmed fixes into automated regression tests.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.