Master debugging by using print statements the right way
TL;DR
Print statements are the fastest way to see what your code is doing.
Use a small set of repeatable print patterns to expose inputs, branches, loops, recursion depth, and thread order in multithreading examples.
When i started programming one of the things that helped me majorly was the ability to add print statements anywhere. New programmers get stuck because code feels invisible. When you start out programming most of the programs you can write will have stuff that you can print out for example, just as a preface i wanted to mention that incase you did not want/or necessarily bog yourself down by learning to use a debugger you could reduce that friction by using print statements. I plan to enunciate this concept with a few use cases.
Quick print tips that help across all use cases:
add labels so you know what a number/string belongs to:
print(”i:”, i)not justprint(i).make the prints easy to scan: use short prefixes like
[STEP],[EDGE],[OK].show both the value and the type when you’re confused:
print(”x=”, x, type(x)).for strings or whitespacey stuff, show the repr:
print(repr(s))so you can see hidden chars.when order matters, include a simple counter or timestamp.
Use Case #1 Trace values for pattern spotting
Write a program to test whether a given string has a palindromic substring
You can probably one shot this program using an LLM nowadays, but the goal here is to learn. so as you probably go through the problem one of the ways would be to prints all substrings, look at them visually and then see if you spot patterns. Once you spot a pattern you will probably be able to code it out. Now that code might have bugs, so one of the way you can probably start to debug that is voila Add print statements and figure out why is the edge case not what you expect.
Use Case # 2: Print recursion depth to make call order click
Recursion is a concept that a lot of programmers struggle to get their head around.A simple hack is to put down a print statement in the parent function and the function you plan to recurse over. And add something like print(“Printing from current function”). This will give you a picture in your head on the sequence of instructions getting executed. Our brains love to see sequential ordering and having print statements will let you visualize this .
What: Add depth markers and entry/exit prints in recursive functions.
Why: Humans love sequences and depth shows the stack growing and shrinking. With arrows and indents, we can literally see “go down, come back up,” which fixes mental models fast.
Use Case #3 Timestamp threads to spot flow of threading
When learning programming an important concept that we learn, and just for context, multithreading is when you run a program using multiple threads so that the work that a single thread can do is now being done by multiple threads and the coder can then either consolidate those results or use them individually. The hard part here is sometimes figuring out bugs, and one of the ways to do it is to plug in print statements for what you expect to happen at the end for example.
What: In multithreading, add thread name + timestamp to prints.
Why: Timestamps reveal unexpected order
Food for thought
Where did a print-first approach save you time when you first started programming.

