How to Debug Your Code in a Live Coding Interview
Debugging in a live coding interview is one of the highest-leverage skills almost no one practices. Most candidates write code, run it, watch it fail, and then stare at the screen in silence — exactly the wrong response. If this is your first coding interview, the silence feels even longer. A methodical, verbal debugging process can actually improve your hiring signal even when your first solution has bugs.
Why Debugging Matters More Than You Think
Here's what most candidates don't realize: interviewers expect bugs. They set time limits specifically because they know candidates won't write perfect code under pressure in 40 minutes. Interview anxiety makes this worse — the pressure to be perfect on the first try is a common trap.
What they're evaluating is how you respond to failure:
- Do you panic and delete everything?
- Do you silently stare until prompted?
- Or do you systematically trace the issue and fix it?
The third response can turn a Lean Yes into a Yes — and directly impacts your hiring signal.
The Debugging Process Flow
The diagram below shows the complete debugging workflow you should follow during an interview. Start at the top and work through each step deliberately. Notice how the loop back to "Trace Manually" mirrors the iterative nature of real debugging — you rarely fix everything in one pass.
flowchart TD
A["Code Fails a Test"] --> B["Don't Edit Anything Yet"]
B --> C["Say: Let me trace through"]
C --> D["Trace failing test manually"]
D --> E{"Found the bug?"}
E -->|"No"| F["Categorize: off-by-one, logic, edge case"]
F --> G["Form a hypothesis"]
G --> H["Make ONE targeted fix"]
H --> D
E -->|"Yes"| I["Trace test case again to verify"]
I --> J{"Test passes now?"}
J -->|"No"| F
J -->|"Yes"| K["Run full test suite"]
K --> L{"All tests pass?"}
L -->|"No"| M["Pick next failing test"]
M --> D
L -->|"Yes"| N["Celebrate, move to next problem"]
Step 1: Don't Immediately Change Anything
Your first instinct when a test fails will be to start editing. Resist it. Premature edits based on incomplete diagnosis lead to a chain of bad changes that make things worse and destroy the interviewer's confidence in your process.
Instead, say out loud: "Let me trace through what's happening before I make any changes."
This buys you time, signals composure, and forces you into a diagnostic mindset.
Step 2: Trace the Failing Test Case Manually
Take the test case that failed and trace it through your code line by line, annotating the values of each variable as you go.
Do this out loud:
"Okay, nums is [3, 2, 4] and target is 6. We enter the loop. i = 0, num = 3. complement = 6 - 3 = 3. Is 3 in seen? seen is empty, so no. We add seen[3] = 0. i = 1, num = 2. complement = 6 - 2 = 4. Is 4 in seen? No. We add seen[2] = 1. i = 2, num = 4. complement = 6 - 4 = 2. Is 2 in seen? Yes! seen[2] = 1. So we return [1, 2]. But the expected output was [1, 2]... so why is the test failing?"
This narration often surfaces the issue before you've written a single new character. And even if it doesn't, the interviewer sees a systematic thinker — not someone panicking.
Step 3: Identify the Category of Bug
Once you've traced through the failing case, categorize the bug before fixing it:
| Category | Symptoms | Common causes |
|---|---|---|
| Off-by-one | Returns wrong index, misses last element | < vs <=, 0-indexed vs 1-indexed |
| Wrong return value | Output type or structure is wrong | Returning index instead of value |
| Missing edge case | Fails on empty input, single element, duplicates | Didn't consider the edge case in design |
| Logic error | Algorithm is fundamentally wrong for the problem | Misunderstood the problem or chose wrong approach |
| Initialization error | First iteration fails | Forgot to initialize variable, starts at wrong value |
Naming the category out loud helps: "I think this is an off-by-one — let me check the loop bounds."
Step 4: Fix One Thing at a Time
Once you've identified the bug, make the smallest possible change that addresses it. Don't refactor while fixing. Don't add new logic. Don't clean up variable names. Fix the specific bug.
After fixing: trace through the failing test case again manually to verify your fix works before running the code again.
If you run the code and a new test fails, repeat the process from Step 1. Don't stack fixes until you've diagnosed each failure independently.
Step 5: Add Print Statements Strategically (When Allowed)
In most interview environments (CoderPad, HackerRank), you can add print statements to inspect intermediate values. Use them deliberately:
def two_sum(nums, target):
seen = {}
for i, num in enumerate(nums):
complement = target - num
print(f"i={i}, num={num}, complement={complement}, seen={seen}") # debug
if complement in seen:
return [seen[complement], i]
seen[num] = i
return []
Print the exact values the failing test case produces. Look for where the actual value diverges from what you expected.
Always tell the interviewer: "I'm going to add a print statement to inspect the state at each step." Don't do it silently.
What Not to Do
Don't delete and rewrite. Unless you've confirmed your entire approach is wrong, deleting your code and starting over is almost always the wrong move. It signals panic and wastes time. Fix what you have.
Don't go silent. Sixty seconds of silence while staring at code reads as being stuck. Keep narrating even when you don't know the answer: "I'm checking the loop termination condition..."
Don't guess and check. Randomly changing values until a test passes is visible to the interviewer and reads poorly. Every change should be based on a hypothesis.
Don't fix bugs the interviewer didn't reveal. If one test failed, fix that one test. Don't preemptively "fix" code that might be working. You might introduce new bugs. These are some of the most common mistakes that tank your hiring signal.
A Real Debugging Script
Here's a complete script to use the moment a test fails:
"Okay, that test failed. Let me not change anything yet — I want to trace through what's happening. [Trace manually out loud.] I see — at this step, the value I'm computing is X but I expected Y. I think the issue is [category of bug]. Let me make one targeted change: [describe the change]. Now let me trace through the test case again to verify this fixes it before running. [Trace.] Yes, that looks right now. Let me run it."
This script takes 60–90 seconds. It consistently produces better outcomes than jumping straight to edits.
Debugging Techniques by Language
Different languages have different debugging patterns. Here are the language-specific techniques that work best in interview settings.
Python
Python is forgiving for debugging because you can quickly add prints and see variable state. Use f-strings for clean output:
# Quick variable inspection
print(f"x={x}, y={y}, result={result}")
# Inspect data structures
print(f"list: {my_list}, dict: {my_dict}")
# Check types when something unexpected happens
print(f"type(x)={type(x)}, type(y)={type(y)}")
# Trace loop progress
for i, item in enumerate(items):
print(f"iteration {i}: item={item}")
Python-specific pitfalls in interviews:
- Forgetting that
range(n)goes from 0 to n-1 - Mixing up
appendandextendon lists - Not realizing
dict.keys()anddict.values()return views, not lists - Using
isinstead of==for value comparison - Off-by-one with
len(s) - 1when accessing string indices
Java
Java gives you compile-time type checking, so many bugs surface before runtime. Focus on logical errors:
// Quick debugging prints
System.out.println("x=" + x + ", y=" + y);
// Check array contents
System.out.println(Arrays.toString(array));
// Trace object state
System.out.println("Node{" + "val=" + node.val + ", next=" + node.next + "}");
Java-specific pitfalls in interviews:
- Integer overflow when summing large numbers — use
long - Null pointer exceptions on uninitialized objects
==comparing references instead of values for objects- Forgetting to handle empty arrays or null inputs
- Off-by-one in
for (int i = 0; i < arr.length; i++)
JavaScript
JavaScript has loose typing and quirky behavior that trips up candidates:
// Quick debugging
console.log(`x=${x}, y=${y}`);
console.log('array:', JSON.stringify(arr));
// Check for undefined vs null
console.log('value:', val, 'type:', typeof val);
// Trace function calls
console.log('entering function with args:', arg1, arg2);
JavaScript-specific pitfalls in interviews:
===vs==causing unexpected coercion- Array methods that mutate vs return new arrays (
splicevsslice) thiskeyword behavior changing in callbacksparseIntwithout radix parameter- Async/await issues — forgetting to
awaitpromises
Common Bugs in Interviews
Certain bugs appear again and again in interview settings. Knowing them helps you spot them faster.
Off-by-One Errors
The single most common bug. You loop one too many or one too few times.
# BUG: misses the last element
for i in range(len(arr) - 1): # should be range(len(arr))
if arr[i] > arr[i + 1]:
return False
# FIX:
for i in range(len(arr) - 1): # this is correct for adjacent comparison
if arr[i] > arr[i + 1]:
return False
How to spot it: Your code works for small inputs but fails on the boundary. The last element is wrong, or the first element is skipped.
How to fix it: Draw out the loop indices for a small input (3-4 elements). Write down the value of i at each step. Compare with what you expect.
Null Pointer / Undefined Errors
Accessing a property or method on a value that doesn't exist:
// BUG: node could be null
function traverse(node) {
node.left.val // crashes if node is null
}
// FIX:
function traverse(node) {
if (node === null) return;
console.log(node.val);
traverse(node.left);
}
How to spot it: Error says "Cannot read property of null/undefined" or NullPointerException.
How to fix it: Add a null check before every property access. Ask yourself: "Can this variable be null/undefined at this point?"
Infinite Loops
Your code never terminates because the loop condition never becomes false:
# BUG: i never changes
i = 0
while arr[i] != target:
result.append(arr[i])
# forgot to increment i
# FIX:
i = 0
while i < len(arr) and arr[i] != target:
result.append(arr[i])
i += 1 # now i advances
How to spot it: The code hangs or times out. In an interview, you'll notice the evaluator looking concerned.
How to fix it: Check every variable that controls the loop. Make sure it changes every iteration. Add a print statement inside the loop to confirm it's progressing.
Incorrect Base Cases
Recursive functions fail because the base case is wrong or missing:
# BUG: no base case for empty list
def sum_list(lst):
return lst[0] + sum_list(lst[1:]) # crashes on empty list
# FIX:
def sum_list(lst):
if not lst: # base case
return 0
return lst[0] + sum_list(lst[1:])
How to spot it: Stack overflow error, or the function never returns.
How to fix it: Always write the base case first. Test it mentally with the smallest possible input (empty input, single element).
Index Out of Range
Accessing an array index that doesn't exist:
// BUG: arr[len] is out of bounds
int lastElement = arr[arr.length]; // should be arr.length - 1
How to spot it: ArrayIndexOutOfBoundsException or RangeError.
How to fix it: For zero-indexed arrays, the last valid index is length - 1. Write a quick check: "Does index i always stay within [0, length-1]?"
The Rubber Duck Method
The "rubber duck debugging" technique comes from a story about a programmer who kept a rubber duck on his desk. When stuck, he'd explain the code to the duck line by line — and often found the bug in the process of explaining.
In an interview, you don't have a rubber duck, but you have something better: the interviewer. Use them.
How to Use the Rubber Duck Method in Interviews
Start with the big picture. Say: "Let me walk through my approach one more time before debugging." Explain what your code is supposed to do.
Go line by line. Narrate each line's purpose and expected output. "This line finds the complement. This line checks if it's in the hash map. If it is, we return early."
State your assumptions. "I'm assuming that
numsalways has at least two elements because the problem says so." This surfaces hidden assumptions that might be wrong.Ask the interviewer questions. "When I reach this line, what should the value of
xbe?" This is not cheating — it's demonstrating your thought process.
Why This Works
The act of verbalizing forces you to slow down. You can't hand-wave past a bug when you're explaining every line out loud. Your brain also processes language differently than code — explaining in plain English often reveals when your logic doesn't actually make sense.
What Interviewers See
When you rubber-duck with the interviewer, they see:
- A candidate who can communicate technical ideas clearly
- Someone who can break down complex logic into steps
- A person who knows when and how to ask for help
- Confidence even in the face of failure
These are all strong hiring signals.
Time Management During Debugging
Debugging can eat your entire interview if you let it. Here's how to manage your time.
The 5-Minute Rule
If you've spent 5 minutes on a single bug without making progress, step back and ask for help. Say:
"I've been working on this for a few minutes and I think I'm going in circles. Could you give me a hint about whether my approach is correct, or if there's something specific I'm missing?"
This is far better than spending 15 minutes silently struggling.
Time Allocation for a 45-Minute Interview
| Phase | Time | What to Do |
|---|---|---|
| Understanding the problem | 5 min | Ask clarifying questions, confirm examples |
| Designing the solution | 5 min | Talk through approach before coding |
| Writing the code | 15-20 min | Implement the solution |
| Debugging | 10-15 min | Fix failing tests |
| Final review & cleanup | 5 min | Walk through edge cases |
Notice that debugging gets 10-15 minutes, not 30. If debugging takes longer, your approach may be wrong, and you need to communicate that to the interviewer.
When to Ask for a Hint
Ask for a hint when:
- You've traced the code three times and can't find the bug
- You suspect your approach is fundamentally wrong
- You're about to delete and rewrite (ask first)
- You're running out of time and still have failing tests
How to ask for a hint gracefully:
"I've traced through this twice and I see the issue — the output is wrong when the input has duplicates. But I can't figure out why my logic doesn't handle it. Do you have a suggestion?"
This shows you've done the work and are stuck at a specific point, not just asking for the answer.
When to Move On
Sometimes the right move is to leave a bug unfixed and move to the next part of the problem. Say:
"I'm going to leave this bug for now and implement the rest of the solution. I can come back to it if time allows."
This shows prioritization skills and ensures you demonstrate breadth even if depth is lacking.
Interviewer's Perspective
Understanding what the interviewer is actually watching for during debugging changes your entire approach.
What Interviewers Evaluate During Debugging
Process over outcome. They care more about how you debug than whether you fix the bug. A candidate who systematically traces and communicates clearly is often rated higher than one who fixes the bug silently through intuition.
Composure under pressure. Bugs are stressful. Interviewers want to see if you can stay calm, think clearly, and communicate when things go wrong — because that's what the job is like.
Communication quality. Can you explain your thought process? Can you articulate what you think is wrong? Can you describe what you're changing and why?
Learning speed. When you make a fix, do you immediately understand why the old code was wrong and the new code is correct? Or do you just try random changes?
Independence vs. collaboration. There's a sweet spot. You should be able to debug independently for a reasonable time, but you should also know when to ask for help. Candidates who never ask for hints seem arrogant or lost. Candidates who ask immediately seem helpless.
Common Interviewer Signals to Watch For
Interviewers will often:
- Nod or say "mm-hmm" when you're on the right track
- Stay silent when you're going in the wrong direction
- Ask "are you sure?" when you're about to make a risky change
- Suggest a test case when they want you to focus on a specific scenario
- Glance at the clock when time is running short
Read these signals. If the interviewer seems to be encouraging you, keep going. If they seem hesitant, consider asking for a hint.
What Bad Debugging Looks Like
- Editing code while explaining (shows scattered thinking)
- Making multiple changes at once (makes it impossible to tell what fixed the bug)
- Saying "I don't know" repeatedly without trying anything
- Getting visibly frustrated or defensive
- Asking for the answer without attempting to debug
Recovery Strategies
Sometimes a bug is too deep, your approach is fundamentally wrong, or you're running out of time. Here's how to recover gracefully.
Strategy 1: Acknowledge and Pivot
If your approach is wrong, acknowledge it immediately and propose a new direction:
"I think my approach has a fundamental flaw — using a single pass won't work here because I need to consider all pairs. Let me switch to a two-pointer approach instead."
This shows intellectual honesty and the ability to course-correct.
Strategy 2: Simplify the Problem
If you're stuck on a complex case, simplify:
"Let me first get this working for the simple case where all elements are unique. Then I'll handle duplicates."
This breaks a complex debugging session into manageable pieces.
Strategy 3: Trade Time for Correctness
If you're running out of time, implement a simpler (even if less optimal) solution:
"I'm running low on time, so let me implement the O(n²) brute force solution to make sure it's correct. I can note the optimization if you'd like."
Getting a correct brute force solution is better than a broken optimal one.
Strategy 4: Partial Credit Through Partial Solutions
If you can't fix everything, fix what you can and explain what's left:
"I've gotten the core logic working for the standard case. I still need to handle the edge case of empty input, but I can describe how I'd approach that..."
This shows you understand the problem even if you couldn't finish.
Strategy 5: The Honest Reset
If everything is broken, be honest:
"I think I've introduced too many changes and made things worse. Let me delete the last few changes, go back to where I was, and try a more focused approach."
This is better than continuing to pile on fixes. It shows self-awareness and the ability to recover from mistakes.
Practice Exercises
The only way to get better at debugging is to practice. Here are exercises you can do before your interview.
Exercise 1: Bug Injection
Take a working solution and introduce 3-5 bugs. Time yourself as you find and fix them. Bugs to inject:
- Change
<to<=in a loop - Remove a base case from a recursive function
- Swap two variable assignments
- Add an off-by-one in an array index
- Change a return value
Exercise 2: Reading Broken Code
Find solutions online (LeetCode discussions, GitHub) that have bugs. Read through them and identify the bugs without running the code. This builds your static analysis skills.
Exercise 3: Debug Under Time Pressure
Set a 10-minute timer. Open a problem you've solved before, but introduce a bug. Debug it while narrating your process out loud. Record yourself if possible. Watch it back and look for:
- Long silences (you should be narrating)
- Random changes (every change should have a hypothesis)
- Panic (you should stay calm)
Exercise 4: Edge Case Enumeration
Before running your code, list every edge case you can think of:
- Empty input
- Single element
- All elements the same
- Maximum size input
- Negative numbers (if applicable)
- Null/undefined values (if applicable)
Then trace through at least 2-3 of these manually.
Exercise 5: The Explanation Challenge
Explain your debugging process to a friend or colleague who doesn't code. If they can follow your explanation, you're communicating clearly. If they get lost, simplify your narration.
Debugging Checklist
Use this checklist the moment a test fails. Run through it in order.
- Pause. Don't edit anything yet.
- Narrate. Say: "Let me trace through what's happening."
- Read the error message. What does it actually say? What line number?
- Trace manually. Walk through the failing test case line by line.
- Identify the category. Off-by-one? Logic error? Edge case? Initialization?
- Form a hypothesis. "I think the bug is [X] because [Y]."
- Make one change. The smallest possible fix for your hypothesis.
- Verify. Trace the test case again to confirm the fix.
- Run. Execute the test suite.
- Repeat. If more tests fail, go back to step 3.
Quick Reference: Common Fixes
| Symptom | First thing to check |
|---|---|
| Wrong index returned | Off-by-one in loop bounds |
| Empty result when expected values | Check if hash map lookup is correct |
| Stack overflow | Missing or wrong base case |
| NullPointerException | Add null check before property access |
| Time limit exceeded | Infinite loop or O(2^n) complexity |
| Wrong output type | Check return statement matches expected format |
Real Debugging Scenarios
Here are three complete walkthroughs of debugging in interview-style scenarios.
Scenario 1: Two Sum with Off-by-One
Problem: Given an array of integers and a target, return indices of two numbers that add up to target.
Your code:
def two_sum(nums, target):
seen = {}
for i in range(1, len(nums)): # BUG: starts at 1 instead of 0
complement = target - nums[i]
if complement in seen:
return [seen[complement], i]
seen[nums[i]] = i
return []
Test fails: Input [3, 3], target 6. Expected [0, 1]. Got [].
Debugging walkthrough:
- Pause. Don't touch the code.
- Trace. "nums = [3, 3], target = 6. Loop starts at i = 1. nums[1] = 3. complement = 6 - 3 = 3. Is 3 in seen? seen is empty. No. Add seen[3] = 1. Loop ends. Return []."
- Identify the bug. "I skipped i = 0. The loop starts at 1, so the first element is never processed."
- Hypothesis. "If I start the loop at i = 0, the first 3 gets added to seen, and when we process the second 3, we find the complement."
- Fix. Change
range(1, len(nums))torange(len(nums)). - Verify. Trace again: i = 0, nums[0] = 3, complement = 3, not in seen, add seen[3] = 0. i = 1, nums[1] = 3, complement = 3, IS in seen, return [0, 1]. Correct!
- Run. Test passes.
Scenario 2: Binary Search with Infinite Loop
Problem: Search for a target in a sorted array.
Your code:
def binary_search(arr, target):
left, right = 0, len(arr)
while left < right:
mid = (left + right) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
left = mid # BUG: should be mid + 1
else:
right = mid
return -1
Test fails: Input [1, 2, 3, 4, 5], target 5. Program hangs (infinite loop).
Debugging walkthrough:
- Pause. Notice the program isn't finishing.
- Trace. "left = 0, right = 5. mid = 2, arr[2] = 3 < 5. left = mid = 2. left = 2, right = 5. mid = 3, arr[3] = 4 < 5. left = mid = 3. left = 3, right = 5. mid = 4, arr[4] = 5 == target. Return 4."
- Wait — that worked. "Let me try target = 4. left = 0, right = 5. mid = 2, arr[2] = 3 < 4. left = 2. left = 2, right = 5. mid = 3, arr[3] = 4 == target. That worked too."
- Try target = 1. "left = 0, right = 5. mid = 2, arr[2] = 3 > 1. right = 2. left = 0, right = 2. mid = 1, arr[1] = 2 > 1. right = 1. left = 0, right = 1. mid = 0, arr[0] = 1 == target. That worked."
- Infinite loop check. "Let me try target = 6 (not in array). left = 0, right = 5. mid = 2, arr[2] = 3 < 6. left = 2. left = 2, right = 5. mid = 3, arr[3] = 4 < 6. left = 3. left = 3, right = 5. mid = 4, arr[4] = 5 < 6. left = 4. left = 4, right = 5. mid = 4, arr[4] = 5 < 6. left = 4. We're stuck — left never reaches right."
- Identify. "When
arr[mid] < target, settingleft = middoesn't advance past mid when mid is already equal to left." - Fix. Change
left = midtoleft = mid + 1. - Run. All tests pass.
Scenario 3: Recursive Tree Traversal with Null Reference
Problem: Calculate the sum of all values in a binary tree.
Your code:
def tree_sum(node):
if node is None:
return 0
return node.val + tree_sum(node.left) + tree_sum(node.right)
Test fails: Input is a tree with only a root node (left and right are None). Expected 5. Got RecursionError: maximum recursion depth exceeded.
Debugging walkthrough:
- Pause. Stack overflow means infinite recursion.
- Trace. "node = root (val=5, left=None, right=None). node is not None. Return 5 + tree_sum(None) + tree_sum(None)."
- Wait. "tree_sum(None) should hit the base case and return 0. Let me check my base case.
if node is None: return 0. That looks correct." - Check the test setup. "Is the node actually None? Or is it an object with val=None?"
- Re-examine. "Actually, let me check: is
node.leftactuallyNone, or is it some other falsy value?" - Hypothesis. "What if the test is passing in a different data structure? Let me check the TreeNode definition."
- Root cause. "The TreeNode class has
leftandrightattributes, but they default toNoneonly if initialized properly. If someone creates a node without setting left/right, those attributes might not exist at all." - Fix. Use
getattr(node, 'left', None)or checkhasattrbefore accessing. Or initialize properly:node.left = None; node.right = None. - Run. Test passes.
Practice Debugging Live With Alex
The only way to make debugging feel natural is to do it while being observed. Alex on InterviewSkool runs your code against a hidden test suite and asks you to debug in real time — the same experience you'll face in a FAANG interview.
Practice debugging under pressure →
Frequently Asked Questions
What if I genuinely don't know what's wrong after tracing?
Ask for a hint. Say: "I've traced through the failing case and I can see the output is wrong, but I can't pinpoint why. Could you give me a nudge?" This is better than extended silence. Interviewers expect to give hints — how quickly you incorporate one is part of what they're evaluating.
Is it okay to run the code multiple times?
Yes — but only after you've formed a hypothesis each time. Running code repeatedly without changing anything accomplishes nothing. Every run should be testing a specific fix. Interviewers notice when candidates run the same code three times as if the output might change.
Should I test my code before the interviewer runs it?
Always. Walk through at least one example manually before handing it off to run. This catches obvious bugs and shows the interviewer that you validate your work. Interviewers who run code for candidates who haven't tested it first are usually already forming a Lean Yes or lower signal.