You have just finished writing your solution on the whiteboard. The logic feels right. The code looks clean. You turn to the interviewer and say, "I think that's it."
And then they ask: "Can you walk me through it with an example?"
This is the moment where most candidates either shine or stumble. If you have not traced through your code carefully, you are about to discover bugs in real time, in front of someone evaluating you. That is not a great position to be in.
Tracing through your code, sometimes called "dry running," is the practice of manually executing your code step by step with a concrete input. It is the single most effective way to catch bugs before your interviewer does. More than that, it demonstrates a level of rigor and attention to detail that interviewers love to see.
This chapter teaches you how to trace systematically, what to look for, and when to do it so you walk into every interview with a reliable verification process.
Here is a harsh truth about coding interviews: writing code that looks correct is not enough. You need to prove it works. And the quickest proof is running through it yourself with real values.
Think about what happens when you skip tracing:
Every one of these bugs is trivially caught by tracing. Every one of them has tanked interviews for otherwise strong candidates.
Tracing also gives you something less tangible but equally important: confidence. When you trace through your code and see it produce the right answer step by step, you can declare "done" with conviction instead of hope.
The most systematic way to trace code is with a variable table. You create a table where each column represents a variable in your code, and each row represents one step of execution. As you mentally execute each line, you update the values in the table.
This sounds simple, and it is. That is what makes it powerful. It forces you to be precise about what every variable holds at every point in time, which is exactly the level of precision needed to catch subtle bugs.
Let us see this in action with a concrete example. Suppose you wrote a function to find the maximum subarray sum using Kadane's algorithm:
Now trace it with nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]:
By tracing this table, you can verify that currentSum correctly tracks the best subarray ending at each position, and maxSum correctly tracks the global best. If you had a bug, say initializing maxSum = 0 instead of nums[0], the trace would reveal it immediately when dealing with all-negative arrays.
Iterative code is straightforward to trace with a flat table. Recursive code needs something extra: a call stack representation. Each recursive call adds a frame to the stack, and each return pops one off. If you do not track this carefully, you will lose track of which call you are in and what values it holds.
The simplest approach is to use indentation to represent depth. Each recursive call gets indented one level deeper, and the return value is written when the call completes.
Let us trace a recursive Fibonacci function:
Trace for fib(4):
This visualization makes it obvious why naive Fibonacci is O(2^n), you can see the repeated calls. It also makes it easy to verify that the base cases are correct and that values combine properly as they return up the stack.
For backtracking problems, tracing gets trickier because the state changes as you go deeper and then reverts when you backtrack. The key is to track what gets added and removed at each level. Consider generating all subsets of [1, 2, 3]:
Trace for nums = [1, 2, 3]:
Notice how each "add" has a matching "remove" at the same indentation level. If you see an add without a remove, or the current list is wrong at any point, you have found a bug. This symmetry is the hallmark of correct backtracking.
Timing matters. Trace too early and you waste time verifying code that might still change. Trace too late and you have already declared "done" without checking.
Here is the rule: trace after you finish writing your code, before you tell the interviewer you are done.
This is a non-negotiable step. Even if your code looks perfect, even if you are confident, trace it. Here is why: the bugs you are most likely to write are the ones you do not expect. Confidence that your code is correct is not evidence that it is correct.
The tracing workflow looks like this:
Not all examples are equally useful for tracing. Here is how to choose:
| Example Type | Purpose | When to Use |
|---|---|---|
| Small, typical case | Verifies core logic works | Always, this is your primary trace |
| Edge case (empty, single) | Catches boundary errors | After the main trace passes |
| Case that exercises all branches | Ensures no dead code | When your code has if/else branches |
| Previously failing case | Confirms your fix works | After you fix a bug |
A common mistake is picking an example that is too large. You do not need nums = [1, 2, 3, 4, 5, 6, 7, 8] when nums = [3, 1, 4] would exercise the same logic. Smaller examples mean faster tracing, and in an interview, speed matters.
Certain categories of bugs show up over and over in coding interviews. Knowing what to watch for makes your tracing more targeted and effective.
This is the single most common bug in interviews. It shows up in loop boundaries, array indexing, and substring operations.
The pattern: Your loop runs one time too many or one time too few.
When tracing, pay special attention to the first and last iterations of every loop. Ask yourself: does the loop start at the right index? Does it stop at the right index? What happens on the boundary?
Initializing accumulators to the wrong value is sneaky because the code often works for most inputs but fails on edge cases.
Tracing with an all-negative input like [-3, -2, -1] catches this instantly.
Your algorithm works for arrays of length 5 but crashes for length 0 or 1. Tracing with a minimal input reveals whether your code handles the boundaries.
Sometimes the loop body updates the wrong variable, or updates it at the wrong time (before a check instead of after, or vice versa).
Tracing exposes the incorrect ordering because you see the actual values at each step and can spot when something goes wrong.
A missing or incorrect base case leads to infinite recursion or wrong answers. By tracing the recursive calls down to the base case, you verify that the recursion terminates and returns the right value.
A loop invariant is a condition that is true at the start of every loop iteration. Checking invariants during tracing gives you extra confidence that your algorithm is correct, not just for the example you traced, but in general.
For example, in binary search, the invariant is: "the target, if it exists, is within nums[left..right]." At each step of your trace, verify this holds:
If at any step the invariant breaks, your algorithm has a bug. This is a more rigorous check than just verifying the final output, because even a buggy algorithm can produce the right answer for a specific input.
Why does this matter beyond the trace itself? Because if the invariant holds for every iteration you check, you have strong evidence it holds in general. The loop invariant is what makes the algorithm correct for all inputs, not just the one you traced. This kind of reasoning is exactly what interviewers want to see.
| Algorithm | Invariant |
|---|---|
| Binary Search | Target is in nums[left..right] (if it exists) |
| Two Pointers (sorted) | All elements before left and after right are processed |
| Sliding Window | Window [left..right] satisfies the constraint |
| Partition (QuickSort) | Elements before pivot index are smaller, after are larger |
| Merge Sort merge | Output array is sorted up to current position |
Let us put everything together. You have written a function to merge two sorted arrays and you want to verify it:
Trace with nums1 = [1, 2, 3, 0, 0, 0], m = 3, nums2 = [2, 5, 6], n = 3:
The trace confirms the algorithm works correctly. It also reveals something important: we did not need the second while loop for this input, but we can reason that if j were still non-negative, the remaining elements in nums2 are smaller than everything placed so far and would fill the front of the array.
Now let us trace a quick edge case. What about nums1 = [0], m = 0, nums2 = [1], n = 1? This is the case where nums1 has no real elements and all values come from nums2.
Good. The second while loop handles this case correctly. Without tracing, you might have worried about this edge case but not been sure.
A few practical tips to make tracing smoother during actual interviews:
int result = 0.Tracing through your code is the bridge between "I think this works" and "I know this works." The table-trace technique gives you a systematic way to verify iterative code by tracking every variable at every step. For recursive code, the indented call stack method lets you follow the recursion down to base cases and back up.
The key habits to build:
A candidate who finds and fixes their own bugs through careful tracing looks far more competent than one who writes "perfect" code and then cannot explain what it does when challenged. Build this habit now, and it will serve you in every interview you take.