Dry-Run Your Code in a Coding Interview Before the Interviewer Finds the Bug

May 25, 20269 min read
interview-prepcareermock-interviewscommunication
Dry-Run Your Code in a Coding Interview Before the Interviewer Finds the Bug
TL;DR
  • Dry running means committing values to a trace table at every step, not reading code in your head
  • Set up the table first: one column per variable, one per loop condition, and one for output
  • Cross out old values instead of erasing so you can scan column history for unexpected changes
  • The three bugs dry runs reliably catch: off-by-one conditions, wrong return branches, and mutation aliasing
  • Trace three to four steps on a small representative input, then add one edge case for the corner case score
  • Narrating aloud while tracing is scored as evidence in the interviewer's write-up, not just a nice-to-have
  • Declaring "done" without tracing is a documented red flag that costs candidates offers at top companies

You say "I think I'm done." You wait.

The interviewer looks at your code. Then looks at you. Then looks at the code again. Then: "Can you walk me through this with the example on the board?"

That question is not small talk. It's your second chance, dressed up as a casual suggestion. The offer often turns on what happens in the next four minutes. Whether you actually trace through the code or just read it back to yourself out loud and nod confidently.

Dry running your code on a concrete input by hand is one of the most explicitly scored behaviors in a coding interview. The Tech Interview Handbook rubric for the testing dimension includes exactly this: "acting like a debugger and stepping through each line, updating the program's state at each step." One L5 Google candidate received a Lean Hire instead of Strong Hire specifically because they described what they would test rather than actually tracing through. The distinction is everything.

"Reading Code" Is Not the Same as Simulating It

When most people "dry run," they read the code. Eyes move top to bottom, the brain fills in values from memory, and they nod along until it looks right.

That is not a dry run. That's just reading.

Screenshot of tweet: "i'm merging it. fuck the tests" with quote tweet "writing testcases for your code is doubting your own coding abilities. it's a sign of weakness"

The developer who skips the trace and the developer who gets caught in the walkthrough are frequently the same developer.

The problem is that your brain already has a model of the code, and it will make the code match that model. That's exactly how bugs survive code review. You wrote it, so your mental model and the actual code are deeply entangled. Reading doesn't stress-test the entanglement. Tracing does.

A dry run forces you to commit. At every line, you write down an actual value. If your code says result += arr[i] and you haven't written down what arr[i] is at this point in execution, you can't move on. The moment you get to a line and realize you don't know the current value, you've found the gap. That gap is usually where the bug lives.

Build the Table Before You Read Line One

The most common dry-run mistake is starting to trace without a table. People write a value or two in the margin, track a couple iterations mentally, then lose the thread. Set up the table before you execute a single line.

Structure it like this:

  • One column per variable currently in scope
  • A condition column for each loop (records True or False on every evaluation)
  • A step column on the left (iteration number or line number, whichever is clearer)
  • An output column on the right if the function produces explicit output

When a variable changes, cross out the old value and write the new one in the same cell. Don't erase. Crossed-out values are data.

Here's the skeleton for tracing a two-pointer algorithm on [2, 4, 6, 8] looking for a pair that sums to 7:

step | left | right | arr[l] | arr[r] | sum | l < r?
-----|------|-------|--------|--------|-----|-------
init |  0   |   3   |   2    |   8    |  10 |   T
  1  |  0   |   2   |   2    |   6    |   8 |   T
  2  |  0   |   1   |   2    |   4    |   6 |   T
  3  |  1   |   1   |   -    |   -    |   - |   F  → exit, return False

Three iterations, one edge case (converging pointers), and loop termination is visually confirmed. The condition column at step 3 tells you exactly why the loop stops and what gets returned. Without it, you might not notice the function exits returning False rather than checking arr[1] + arr[1]. That's a correct behavior you can now explicitly confirm.

A Complete Dry Run: Catch the Bug in Progress

Say you write this function to find the maximum value in a sliding window of size k:

def max_in_windows(arr, k): result = [] for i in range(len(arr) - k): # bug: should be range(len(arr) - k + 1) window_max = max(arr[i:i+k]) result.append(window_max) return result

Input: [3, 1, 2, 5, 4], k = 3. Expected output: [3, 5, 5].

step | i  | range(5-3) | arr[i:i+3] | window_max | result
-----|----|-----------:|------------|------------|--------
init |  0 |  range(2)  | [3, 1, 2]  |      3     | [3]
  1  |  1 |            | [1, 2, 5]  |      5     | [3, 5]
exit |    | i reaches 2, loop ends

Output: [3, 5]. Expected: [3, 5, 5]. The final window [2, 5, 4] never ran.

The trace table makes the off-by-one impossible to miss. range(len(arr) - k) gives range(2), which produces indices 0 and 1. The last valid starting index is 2. You see this the moment you write the range in the table. Without it, range(len(arr) - k) looks plausible because the formula has the right shape.

Fix it to range(len(arr) - k + 1). The range becomes range(3), giving indices 0, 1, 2. Third window runs. Output matches. Done.

Cross Out, Never Erase

When you erase old values, you destroy the history of how state evolved. When you cross them out, you can scan down any column and read the rhythm of change. A variable that changes every step is your loop index. A variable that changes every few steps is your accumulator. A variable that changes in a way you didn't expect is your bug.

The column scan is one of the fastest bug-finding techniques available. If right is supposed to be decreasing but crosses out to a larger value in one step, your pointer is moving the wrong direction. If result never gets crossed out inside the loop, you may have the append in the wrong place.

This also surfaces aliasing bugs. When you trace a linked list reversal and write curr.next = prev, you have to commit to what curr.next now points to. Then prev = curr means updating prev in your table. If you're ever unsure what a pointer points to because you changed something three lines ago, the table makes the gap immediate. You can't hand-wave it.

The Bugs That Only Show Up on Paper

Three categories are nearly invisible during code review but appear in the first complete trace.

Side-by-side meme: "During technical interviews" showing composed Pope Francis vs "During bug fixing" showing haggard disheveled man

The difference between "I'm done" and "I've traced it" is two minutes and one of these phases.

Off-by-one in loop conditions. The condition column catches this every time. If your loop says while left <= right and you expected three iterations but see four True entries, you spot the problem before it causes a wrong answer. Without the table, < and <= look almost identical and your brain picks whichever one it expects. For more on this, see Your Binary Search Has an Off-by-One Bug.

Return value in the wrong branch. Many solutions have multiple return paths. Tracing forces you to follow one actual path from start to finish. If the function returns None when you expected 0, you see it in the table. Code review lets you imagine control flow. Tracing makes you commit to one path.

Mutation aliasing. When two variables reference the same list or object, modifying through one changes what the other sees. Common in Python with list arguments, and in any language when passing objects to helper functions. During a trace, two variables you assumed were independent will converge to the same concrete value at the same step. When they do, the bug is visible.

How to Dry-Run in a Coding Interview Without Eating Your Time

A dry run in an interview does not mean tracing 20 elements. Pick the smallest input that exercises every branch.

For a two-pointer problem: four or five elements, with a case where both pointers move. For a sliding window: three or four elements with a window that forces at least two slides. For a recursive function: a tree or list with three to four nodes.

After the typical case, do one edge case. Usually: empty input, single element, or all duplicates. This second trace takes sixty seconds and is what separates a "found and handled corner cases" score from a "tested basic case only" score.

Narrate as you go. Don't trace in silence. Say what you're writing, why the variable changes, what the loop condition evaluates to. Interviewers score this narration separately from the trace itself. It's how they see your reasoning, not just your result.

If narrating under pressure feels like trying to pat your head and rub your stomach at the same time, practice until it's automatic. That's what SpaceComplexity is built for: voice-based mock interviews where you narrate through a problem and get scored on reasoning, not just the final answer.

Recap

  • A dry run means committing values to a table, not reading code in your head
  • Columns: one per variable, one per loop condition, one for output
  • Cross out old values instead of erasing; the history is useful
  • Three to four steps on a small representative input, then one edge case
  • Narrate as you trace; the communication score depends on it
  • Three bugs dry runs reliably catch: off-by-one conditions, wrong return branches, mutation aliasing
  • Declaring "I'm done" without tracing is a documented red flag; see Coding Interview Red Flags
  • What the interviewer writes about your verification shapes the scorecard directly; see What Your Interviewer Is Writing While You Think

One more thing: after the trace, look at the interviewer. Ask if they want you to test any other cases. The answer tells you whether they spotted something you missed, and asking shows you know the trace is a starting point, not a declaration of perfection.

Further Reading