Tutorials

Stop Using print() to Debug Python (icecream Does the Labeling)

· Tutorials

icecream prints variable names, values, and locations for you. Replace print() debugging with ic() in five minutes.

Stop Using print() to Debug Python (icecream Does the Labeling)

Last updated: November 6, 2026 · 3-minute read

Every Python developer has been guilty of "print-debugging." When a function misbehaves, setting up a full interactive debugger like pdb or configuring breakpoints in VS Code often feels like overkill for a quick check.

So what do we do? We write code like this:

Then, once the bug is fixed, you face the tedious cleanup chore: scouring files to delete dozen of temporary print() statements, praying you didn't accidentally leave one inside an API loop that floods your production logs.

There is a vastly better way. Meet icecream, a sweet, lightweight Python library that replaces messy print() calls with clean, auto-labeled, inspectable debugging outputs.

Here is why icecream should be in every Python developer's toolkit, and how it eliminates the friction of daily debugging.

Why icecream Beats print for Debugging

When you pass a variable or expression to ic(), it doesn't just print the value. It prints the variable name, the value, and formats data structures with syntax highlighting automatically.

userprofile = {"id": 1042, "role": "admin", "active": True} userscore = 98.5

# The old, tedious way: print(f"userprofile: {userprofile}, userscore: {userscore}")

# The icecream way: ic(userprofile, userscore)

In your terminal, icecream outputs:

No f-strings. No repetitive typing of variable names. icecream uses Python's abstract syntax tree (AST) inspection under the hood to determine the exact expressions you passed to it.

Zero-Argument Execution Tracing

Ever scattered print("HERE") or print("STEP 3") across an if/elif/else block to see which branch executed?

With icecream, just call ic() with no arguments:

ic() automatically inspects the call stack and prints the filename, line number, and function name. You immediately know where your code branched without inventing silly label strings.

Pass-Through Return Values

This is the feature print() can never match. print() returns None, meaning you cannot insert it into an active expression without breaking your code.

In contrast, ic() returns whatever argument you pass into it:

calculateinvoice(100.0, 0.18, 15.0)

Terminal output: text ic| subtotal (1 + taxrate): 118.0 ic| discount: 15.0

You can inspect intermediate values inside list comprehensions, return statements, and function calls on the fly.

One-Line Global Disabling

The worst part of print-debugging is deleting every statement before committing your code. With icecream, you disable all outputs globally across your entire module with a single command:

# When you deploy or commit, simply disable: ic.disable()

ic("This will never print to your console!")

# Need it back? Re-enable anytime: ic.enable()

If you leave ic() statements in your code while disabled, they execute with near-zero overhead and print absolutely nothing.

Custom Prefixes and File Logging

By default, icecream prefixes outputs with ic| . You can customize this prefix, include timestamps, or redirect outputs directly into a logger or debug file:

logging.basicConfig(level=logging.DEBUG)

ansaribilal.com — technology, tested in public.