LeetCopilot Logo
LeetCopilot
Home/Blog/How to Debug LeetCode Solutions When Test Cases Fail: A Systematic Approach

How to Debug LeetCode Solutions When Test Cases Fail: A Systematic Approach

Marcus Liu
Nov 21, 2025
17 min read
DebuggingLeetCodeProblem SolvingTestingEdge CasesInterview Prep
Your code passes half the test cases and fails the rest. You stare at the red X, unsure where to begin. Learn the step-by-step debugging framework that transforms failing test cases from frustrating mysteries into solvable puzzles.

You submit your solution with confidence. The first few test cases pass—green checkmarks line up. Then: Test Case 23/47: Failed.

You click to see the input. It's a massive array: [5,2,8,1,9,3,7,4,6,...] (200 elements). Expected output: 42. Your output: 39.

Where do you even start?

If you're like most beginners, you might:

  • Stare at the code hoping the bug jumps out
  • Change random things and resubmit
  • Give up and look at the solution

None of these work. What you need is a systematic debugging process—a methodical way to isolate the bug, understand what's failing, and fix it with confidence.

This guide will teach you exactly that. By the end, you'll have a repeatable framework for debugging any LeetCode solution, turning failed test cases from dead ends into valuable learning opportunities.

TL;DR

  • The Problem: Random debugging (staring at code, changing random lines, giving up) doesn't work because you're guessing instead of diagnosing—bugs hide in specific patterns that require systematic investigation
  • Why It Matters: Debugging failures teaches edge cases and boundary conditions better than solving new problems; skipping it means missing the deepest learning opportunities in interview prep
  • Core Framework: 6-step systematic process: (1) Understand what failed, (2) reproduce locally, (3) trace execution with prints/debugger, (4) compare expected vs actual state, (5) form hypothesis and test fix, (6) test edge cases
  • Common Mistake: Changing multiple things at once or assuming the test case is wrong—99% of the time your code has a bug, and isolated fixes reveal exactly what it is
  • What You'll Learn: How to diagnose off-by-one errors, incorrect base cases, logic errors, and state management bugs using print statements, manual tracing, and pattern recognition

Why Random Debugging Doesn't Work

Before we dive into what works, let's understand why most debugging attempts fail.

The "Guess and Check" Trap

What it looks like: You spot something that "might" be wrong (an off-by-one error, a condition that seems suspicious), change it, resubmit, and hope it works.

Why it fails: Without understanding why the test case failed, you're just guessing. You might accidentally fix one bug but introduce another, or waste time changing code that was already correct.

The "Stare at the Code" Approach

What it looks like: Reading through your solution line by line, hoping to spot the bug through inspection alone.

Why it fails: Your brain sees what you intended to write, not what you actually wrote. Bugs are invisible to static inspection, especially logic errors that only surface with specific inputs.

The "Too Hard, Skip It" Mindset

What it looks like: When a test case fails, you quickly move to another problem or just read the editorial.

Why it fails: Debugging is where the deepest learning happens. By skipping it, you miss the chance to internalize edge cases, boundary conditions, and the subtle mistakes that differentiate beginner code from production-ready code.

The Systematic Debugging Framework

Here's a step-by-step process that works for any failed test case, from easy to hard problems.

Step 1: Understand What Failed (Problem Diagnosis)

Before touching any code, you need to understand the failure.

Ask these questions:

  1. What was the input?
  2. What was your output?
  3. What was the expected output?
  4. Can you reduce the input to a simpler example?

Example:

Let's say you're solving "Maximum Subarray" and test case 15 fails:

  • Input: [-2,1,-3,4,-1,2,1,-5,4]
  • Expected: 6 (subarray [4,-1,2,1])
  • Your output: 4

Key observation: Your output is smaller than expected. This suggests you're finding a valid subarray, but not the maximum one. The bug is likely in how you're tracking or comparing sums.

Step 2: Reproduce Locally with a Small Test Case

Don't debug on LeetCode's platform. Copy the failing input and run it locally where you can print variables, step through execution, and iterate quickly.

Create a minimal test case:

python
def maxSubArray(nums):
    # Your solution here
    pass

# Test locally
test_input = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
result = maxSubArray(test_input)
print(f"Result: {result}, Expected: 6")

Why this works: You now have full control. You can add print statements, modify the input to be even simpler, and iterate without waiting for LeetCode to recompile.

Step 3: Trace Through Execution (Manual or with Prints)

This is the most critical step: watch your code execute step by step with the failing input.

There are two approaches:

Option A: Add Print Statements

Insert print statements at key points to see what your code is actually doing.

Example:

python
def maxSubArray(nums):
    max_sum = nums[0]
    current_sum = nums[0]
    
    for i in range(1, len(nums)):
        current_sum = max(nums[i], current_sum + nums[i])
        print(f"i={i}, nums[i]={nums[i]}, current_sum={current_sum}, max_sum={max_sum}")
        max_sum = max(max_sum, current_sum)
    
    print(f"Final max_sum: {max_sum}")
    return max_sum

Output:

code
i=1, nums[i]=1, current_sum=1, max_sum=-2
i=2, nums[i]=-3, current_sum=-2, max_sum=1
i=3, nums[i]=4, current_sum=4, max_sum=1
i=4, nums[i]=-1, current_sum=3, max_sum=4
i=5, nums[i]=2, current_sum=5, max_sum=4
i=6, nums[i]=1, current_sum=6, max_sum=5
i=7, nums[i]=-5, current_sum=1, max_sum=6
i=8, nums[i]=4, current_sum=5, max_sum=6
Final max_sum: 6

What you learn: By watching the trace, you can see exactly where your logic diverges from what it should do. In this example, if the output were wrong, you'd see at which iteration max_sum stopped updating correctly.

Option B: Use a Debugger (Step Through Code)

Most IDEs (VS Code, PyCharm, etc.) have built-in debuggers that let you:

  • Set breakpoints
  • Step through line by line
  • Inspect variable values at each step

This is more powerful than print statements but requires some setup.

Step 4: Compare Expected vs Actual State

As you trace, ask: "At this step, what should the values be, and what are they actually?"

If there's a mismatch, you've found your bug.

Example:

You expect max_sum to be 6 after processing index 6, but it's still 4. Why?

Looking at your code, you realize you're updating max_sum before calculating current_sum, so you're always one step behind.

Bug identified: Order of operations error.

Step 5: Form a Hypothesis and Test It

Don't just fix the first thing that looks wrong. Form a hypothesis about the root cause, then test it.

Bad approach: "I'll change this < to <= and see if it works."

Good approach: "The bug is that I'm not including the last element in my window. I think it's because my loop condition is range(n-1) instead of range(n). Let me change that and check if it affects the failing test case."

Test the fix:

python
# Original (buggy)
for i in range(len(nums) - 1):
    # ...

# Fixed
for i in range(len(nums)):
    # ...

Run the test case again. If it passes, verify with other test cases to ensure you didn't break something else.

Step 6: Test Edge Cases

Once you fix the immediate bug, proactively test edge cases to make sure your fix is complete.

Common edge cases for array problems:

  • Empty array: []
  • Single element: [5]
  • All negative numbers: [-1, -2, -3]
  • All positive numbers: [1, 2, 3]
  • Mixed signs: [-2, 1, -3, 4]
  • Large inputs: [1] * 10000

Why this matters: A fix might work for the specific failing test case but introduce a new bug elsewhere.

Common Bug Patterns and How to Spot Them

Certain types of bugs appear repeatedly in algorithmic problems. Recognizing these patterns speeds up debugging.

Pattern 1: Off-by-One Errors

Symptoms: Your output is almost correct, or test cases with boundary values fail.

Where they hide:

  • Loop conditions: range(n) vs range(n-1) vs range(1, n)
  • Array indexing: nums[i] vs nums[i+1]
  • Window size calculations: right - left vs right - left + 1

How to debug:

Print the indices and values at boundaries:

python
for i in range(len(nums)):
    print(f"Processing index {i}, value {nums[i]}")

Check: Are you starting at the right index? Ending at the right index? Including all elements you need?

Pattern 2: Incorrect Base Cases

Symptoms: Your solution fails on small inputs (empty, single element, two elements).

Where they hide:

  • Recursive base cases
  • Initial values in DP arrays
  • Edge conditions before the main loop

How to debug:

Test your solution on the smallest possible inputs:

python
test_cases = [
    [],
    [1],
    [1, 2],
    [1, 2, 3]
]
for tc in test_cases:
    print(f"Input: {tc}, Output: {solution(tc)}")

If any fail, check your base cases.

Pattern 3: Logic Errors in Conditions

Symptoms: Your output is wrong in subtle ways that don't follow a clear pattern.

Where they hide:

  • if conditions: < vs <=, and vs or
  • Update logic: max_val = max(...) in the wrong place

How to debug:

Add print statements for every branch:

python
if condition_a:
    print("Branch A taken")
    # ...
elif condition_b:
    print("Branch B taken")
    # ...
else:
    print("Default branch taken")
    # ...

Check: Are you taking the right branch for the failing test case?

Pattern 4: State Management Bugs

Symptoms: Your solution works on simple inputs but fails when state changes (e.g., sliding window, two pointers, graph traversal).

Where they hide:

  • Not resetting variables between iterations
  • Updating state in the wrong order
  • Forgetting to update all related variables

How to debug:

Print the full state at each iteration:

python
for i in range(n):
    print(f"Iteration {i}: left={left}, right={right}, current_sum={current_sum}, result={result}")
    # ... your logic

Check: Is the state what you expect at each step?

How to Debug When the Test Case Is Too Large

Sometimes the failing test case has hundreds or thousands of elements. You can't manually trace through all of them.

Strategy 1: Binary Search the Input

If the large test case fails, try to find the smallest subset that also fails.

Example:

python
large_input = [5,2,8,1,9,3, ...]  # 200 elements

# Try first half
first_half = large_input[:100]
print(solution(first_half))

# Try second half
second_half = large_input[100:]
print(solution(second_half))

Find which half fails, then repeat with smaller subsets until you isolate a minimal failing case.

Strategy 2: Check for Patterns in the Input

Look at the structure of the failing test case:

  • Are there repeated elements?
  • Is it sorted or unsorted?
  • Does it have extreme values (very large, very small, zero)?

This can give you clues about what edge case your code doesn't handle.

Strategy 3: Add Invariant Checks

Insert assertions that verify your assumptions:

python
def solution(nums):
    max_sum = float('-inf')
    current_sum = 0
    
    for num in nums:
        current_sum += num
        assert current_sum <= sum(nums), "Current sum should never exceed total sum"
        max_sum = max(max_sum, current_sum)
        if current_sum < 0:
            current_sum = 0
    
    return max_sum

If an assertion fails, you immediately know which invariant was violated and where.

Debugging Checklist

Use this checklist every time a test case fails:

  • Do I understand what the input, expected output, and my output are?
  • Can I reproduce the failure locally?
  • Have I added print statements or used a debugger to trace execution?
  • Have I identified where my code's behavior diverges from expectations?
  • Have I tested my fix on the original failing case?
  • Have I tested edge cases to ensure I didn't introduce new bugs?

Debugging Tools and Techniques

Built-in Debugging Features

Many platforms and IDEs offer helpful debugging tools. For example, tools like LeetCopilot allow you to ask questions about specific test failures or generate additional test cases to help isolate bugs—making it easier to understand what went wrong without leaving the problem page.

Manual Visualization

For problems involving data structures (trees, graphs, arrays), draw them out:

Example: If debugging a tree traversal, draw the tree and trace your algorithm's path:

code
     1
    / \
   2   3
  / \
 4   5

Expected traversal: [1, 2, 4, 5, 3]
Your traversal: [1, 2, 3, 4, 5]

Visual inspection often reveals missed edge cases (e.g., not processing right subtrees correctly).

Rubber Duck Debugging

Explain your code line by line to yourself (or an imaginary duck):

"First, I initialize max_sum to the first element. Then, for each subsequent element, I decide whether to extend the current subarray or start a new one..."

Often, the act of explaining reveals the flaw in your logic.

Common Mistakes to Avoid

Mistake 1: Changing Multiple Things at Once

If you change three lines and resubmit, you won't know which change fixed the bug (if any).

Fix: Change one thing at a time and test after each change.

Mistake 2: Not Verifying the Fix

You fix the failing test case but don't check if your change broke other cases.

Fix: After fixing, run all test cases (including the original passing ones) to ensure your fix didn't introduce regressions.

Mistake 3: Ignoring Compiler Warnings or Edge Case Hints

LeetCode sometimes gives hints like "Expected output for an empty array is []" or "Handle negative numbers." Read these carefully.

Fix: If a hint is provided, test that specific edge case explicitly.

Mistake 4: Assuming the Test Case Is Wrong

Occasionally, you might think: "My code is right; the test case must be wrong."

Reality: 99% of the time, the test case is correct. Assuming otherwise wastes time.

Fix: Trust the test case and focus on finding what your code misses.

FAQ

How long should I spend debugging before looking at the solution?

Spend at least 15-20 minutes using this systematic approach. If you're still stuck after methodically tracing execution and testing edge cases, it's okay to seek hints or look at the editorial. But don't skip the debugging process—that's where the learning is.

Should I use print statements or a debugger?

Both are valid. Print statements are faster for quick checks and work anywhere. Debuggers are more powerful for complex issues with nested data structures or recursion. Use whichever you're more comfortable with.

What if I can't reduce a large failing test case to a smaller one?

Look for patterns in the input (sorted, repeated values, extreme numbers) and create your own test cases that match those patterns. Often, handcrafted small test cases reveal the same bug.

Is it normal to spend more time debugging than writing the initial solution?

Absolutely. For complex problems, debugging can take 2-3x longer than writing the first draft. This is part of the learning process and reflects real software engineering work.

How do I get better at debugging over time?

Practice the systematic approach on every failing test case. Over time, you'll start recognizing common bug patterns instantly, and debugging will become faster and more intuitive.

Conclusion

Debugging failing test cases isn't about randomly changing code until something works. It's a systematic process:

  1. Understand the failure — What input failed, and why?
  2. Reproduce locally — Run it where you have full control
  3. Trace execution — Watch your code run step by step
  4. Compare expected vs actual — Find where logic diverges
  5. Form a hypothesis — Identify the root cause
  6. Test the fix — Verify it works for all cases

Mastering this framework transforms debugging from a frustrating guessing game into a methodical problem-solving skill. Every failed test case becomes an opportunity to deepen your understanding of edge cases, boundary conditions, and algorithmic correctness.

The best programmers aren't the ones who never write bugs—they're the ones who can diagnose and fix them quickly and confidently. With this systematic approach, you'll join their ranks.

Want to Practice LeetCode Smarter?

LeetCopilot is a free browser extension that enhances your LeetCode practice with AI-powered hints, personalized study notes, and realistic mock interviews — all designed to accelerate your coding interview preparation.

Also compatible with Edge, Brave, and Opera

Related Articles