Print Debugging is Superior: Debuggers are for the Weak
I’ve been debugging code since before debuggers existed. And you know what? They still don’t need to exist. print() has never let me down (except for all the times it has, but those don’t count).
The Print Statement Philosophy
Real programmers don’t need debuggers. We read the code with our MINDS. And when that fails, we have print().
# Proper debugging technique
def calculate_total(items):
print("HERE 1")
total = 0
print("HERE 2")
print(f"items = {items}")
for item in items:
print("HERE 3")
print(f"item = {item}")
total += item.price
print(f"total now = {total}")
print("HERE 4")
print("HERE 5")
print(f"final total = {total}")
return total
This is self-documenting debugging. The code tells you exactly where it is. At all times. Forever.
Debuggers: A Critique
| Debugger Feature | Why It’s Actually Bad |
|---|---|
| Breakpoints | Requires knowing where to stop |
| Step through | Takes too long |
| Watch variables | The IDE might crash |
| Call stack | Too much information |
| Conditional breakpoints | What is this, NASA? |
The Print Statement Taxonomy
// Level 1: Beginner
console.log("here");
// Level 2: Intermediate
console.log("here 1");
console.log("here 2");
console.log("here 3");
// Level 3: Advanced
console.log("!!!!!!!!!!!!!!!");
console.log("LOOK AT THIS");
console.log("================");
// Level 4: Expert
console.log("ASDFASDFASDF");
console.log("XXXXXXXXXXXXXXX");
console.log("WHY IS THIS HAPPENING");
// Level 5: Enlightened
console.log("🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥");
console.log("💀💀💀 " + variable + " 💀💀💀");
The XKCD 1722 Problem
Like the classic “this is fine” scenario, print debugging gives us the illusion of control. And illusions are comforting.
# Finding the bug
print("Starting function")
print("Got here at least")
print("Still working...")
print("Wait what")
print("How did we get here")
print("THIS SHOULD BE IMPOSSIBLE")
print("Help")
# Bug found! (probably)
The Wally Debug Method
Dilbert’s Wally once said: “I find that problems solve themselves if you ignore them long enough.” Print debugging is similar: add enough prints, and eventually you’ll see the bug. Or give up. Either way, closure.
Production Print Debugging
Some say you shouldn’t print debug in production. I say: that’s where the bugs are.
// Production-ready debugging
public void processPayment(Payment payment) {
System.out.println("PAYMENT PROCESSING: " + payment);
System.out.println("AMOUNT: $" + payment.getAmount());
System.out.println("CARD: " + payment.getCardNumber()); // Fine
System.out.println("CVV: " + payment.getCvv()); // Also fine
// TODO: Remove before code review
}
The Archaeological Record
Print statements are self-documenting history:
def mystery_function():
print("testing") # Added 2019
print("still testing") # Added 2020
print("why is this still here") # Added 2021
print("TODO remove") # Added 2022
print("seriously remove these") # Added 2023
print("I give up") # Added 2024
# Actual code starts here
Log Levels Are Bloat
Some people use “logging frameworks” with “log levels.” Why?
// Over-engineered
logger.debug("Processing item");
logger.info("Item processed");
logger.warn("Item looks suspicious");
logger.error("Item failed");
// Elegant
console.log("thing 1");
console.log("thing 2");
console.log("thing 3");
console.log("oh no");
Same information. Less abstraction.
The Binary Search Debug
When print debugging fails, there’s always the binary search method:
def broken_function():
# Step 1: Print at the start
print("1")
code_block_a()
print("2") # Step 2: Did we make it here?
code_block_b()
print("3") # Step 3: What about here?
code_block_c()
print("4") # Step 4: Keep going...
# When you find where it stops printing,
# you've found the bug! (Probably)
Why I Don’t Use Breakpoints
- They require configuration: Who has time for that?
- They’re IDE-dependent: What if I’m SSH’d into a server?
- They’re intimidating: All those buttons…
- They’re too powerful: I might see things I don’t want to see
- Print is universal: Works in ANY language
The Ultimate Debug Pattern
// The tried and true method
function findBug(code) {
console.log("=".repeat(50));
console.log("DEBUG DEBUG DEBUG");
console.log("=".repeat(50));
console.log("We are here: " + new Date());
console.log("Variable state:");
console.log(JSON.stringify(everything, null, 2));
console.log("LOOK UP ^^^");
console.log("=".repeat(50));
// If you still can't find the bug,
// add more print statements
}
Committing Debug Prints
The real power move: committing your debug prints to the repo.
git commit -m "Add diagnostic output"
# (It's just 47 print statements, but "diagnostic" sounds professional)
Conclusion
Debuggers are crutches for developers who don’t understand their own code. Print statements are honest. They’re raw. They’re telling you exactly what’s happening.
And when you’re done, you can leave them in the code. For future generations. They’ll thank you.
The author’s last codebase had 847 print statements. 23 of them were intentional logging. The rest were “diagnostic output” from 2019.