LeetCopilot Logo
LeetCopilot
Home/Blog/How to Manually Trace Your Code Line by Line to Find Bugs

How to Manually Trace Your Code Line by Line to Find Bugs

David Ng
Dec 4, 2025
14 min read
Beginner guideDebuggingProblem solvingLearning StrategyCode tracing
Master the essential debugging technique that reveals exactly where your logic breaks. Learn to trace code execution by hand, catch bugs before running, and build deeper algorithmic understanding.

Your code fails a test case. The expected output is [1, 3]. Your output is [1, 2].

You stare at the code. It looks correct. You add print statements. You run it again. Still wrong. You can't figure out where the logic breaks.

This is the Invisible Bug Problem: Your mental model of what the code does doesn't match what it actually does. You're debugging blind because you're not systematically tracking execution.

The solution: Manual code tracing—also called "dry running" or "hand tracing"—where you simulate the computer's execution step-by-step on paper.

This guide teaches you how to trace code manually to find bugs faster, understand algorithms deeper, and catch errors before even running your code.

TL;DR

  • Manual tracing = simulation: You play the computer, executing code line-by-line on paper, tracking every variable change.
  • Why it works: Forces you to see exactly what your code does, not what you think it does. Reveals logic errors that runtime errors miss.
  • Core technique: Create a trace table with columns for each variable, rows for each step. Update values as code executes.
  • When to trace: After writing code (before running), when test fails (to find bug), when learning new algorithm (to understand flow).
  • Common mistake: Tracing in your head instead of on paper. Mental tracing skips steps and misses bugs. Write everything down.
  • You'll learn: How to build trace tables, trace loops and conditionals, debug recursive functions, and use tracing to prevent bugs proactively.

Beginner-Friendly Explanations

What Manual Tracing Actually Is

Manual tracing means you become the computer.

Instead of running code and hoping it works, you:

  1. Start with the first line
  2. Execute it by hand (what would the computer do?)
  3. Write down variable values
  4. Move to the next line
  5. Repeat until the end

Example (simple):

typescript
let x = 5;
let y = x + 3;
let z = y * 2;

Trace:

code
Step 1: x = 5
Step 2: y = 5 + 3 = 8
Step 3: z = 8 * 2 = 16

For complex algorithms, this technique reveals exactly where your logic diverges from expectations.

Why Beginners Need This Skill

Relying only on running code means:

  • You only see final output, not intermediate states
  • You miss where the bug occurs, only that it occurs
  • You can't debug hidden test cases (LeetCode doesn't show all inputs)

Manual tracing reveals:

  • Variable values at every step
  • Exactly which line produces wrong results
  • Logic errors that don't cause crashes

Real scenario:

typescript
// Bug: Returns wrong index
function findTarget(arr: number[], target: number): number {
  for (let i = 0; i <= arr.length; i++) {  // BUG: <= should be <
    if (arr[i] === target) return i;
  }
  return -1;
}

Running this: Works on small arrays, crashes on edge cases (index out of bounds).
Tracing this: Immediately shows i reaching arr.length, which is invalid.

This connects to broader problem-solving strategies.

Step-by-Step Learning Guidance

Building Your First Trace Table

A trace table organizes your trace with columns for variables and rows for execution steps.

Template:

StepLineVariable 1Variable 2Variable 3Output/Notes
11initialinitialinitial
22updatedupdated-

How to build:

  1. Identify variables: List all variables in your code
  2. Create columns: One for each variable, plus Step, Line, and Notes
  3. Add rows: One row per meaningful execution step
  4. Fill in values: As you trace, update variable values

Example Problem: Find the sum of array elements.

typescript
function sumArray(arr: number[]): number {
  let sum = 0;
  for (let i = 0; i < arr.length; i++) {
    sum += arr[i];
  }
  return sum;
}

// Test: sumArray([2, 5, 3])

Trace Table:

StepLinesumiarr[i]Notes
120--Initialize sum
23002First iteration
34202sum = 0 + 2
43215Loop continues
54715sum = 2 + 5
63723Loop continues
741023sum = 7 + 3
83103-i = 3, i < 3 is false
96103-Return 10

Result: Expected 10, got 10. Code is correct.

Tracing Loops and Conditionals

Loops require tracking loop variables and conditions.

Key steps:

  1. Before loop: Write initial values
  2. Each iteration: Update loop variable, execute body
  3. After each iteration: Check condition—continue or exit?

Example: Find maximum in array.

typescript
function findMax(arr: number[]): number {
  let max = arr[0];
  for (let i = 1; i < arr.length; i++) {
    if (arr[i] > max) {
      max = arr[i];
    }
  }
  return max;
}

// Test: findMax([3, 7, 2, 9, 5])

Trace:

Stepiarr[i]maxCondition (arr[i] > max)Notes
1-33-Initialize max = arr[0]
21737 > 3? TrueUpdate max = 7
32272 > 7? FalseNo change
43979 > 7? TrueUpdate max = 9
54595 > 9? FalseNo change
65-9-i < 5 is false, exit

Result: Return 9 (correct).

For conditionals: Add a column for the boolean expression, showing True/False evaluation.

Tracing Complex Algorithms (Two Pointers Example)

Two Pointers requires tracking both pointer positions and their movement.

Problem: Remove duplicates from sorted array in-place.

typescript
function removeDuplicates(nums: number[]): number {
  if (nums.length === 0) return 0;
  
  let slow = 0;
  for (let fast = 1; fast < nums.length; fast++) {
    if (nums[fast] !== nums[slow]) {
      slow++;
      nums[slow] = nums[fast];
    }
  }
  return slow + 1;
}

// Test: removeDuplicates([1, 1, 2, 2, 3])

Trace Table:

Stepslowfastnums[slow]nums[fast]ConditionActionArray State
10-1--Initialize[1,1,2,2,3]
201111≠1? FalseNo action[1,1,2,2,3]
302122≠1? Trueslow++, nums[1]=2[1,2,2,2,3]
413222≠2? FalseNo action[1,2,2,2,3]
514233≠2? Trueslow++, nums[2]=3[1,2,3,2,3]
6253-fast<5? FalseExit loop[1,2,3,2,3]
7-----Return 2+1=3Length = 3

Result: First 3 elements are [1,2,3], duplicates removed. Correct.

Visualizable Example: Finding a Bug with Tracing

Problem: Two Sum (find two numbers that add to target).

Buggy Code:

typescript
function twoSum(nums: number[], target: number): number[] {
  const seen = new Map<number, number>();
  
  for (let i = 0; i < nums.length; i++) {
    const complement = target - nums[i];
    
    if (seen.has(complement)) {
      return [i, seen.get(complement)!];  // BUG: Wrong order
    }
    
    seen.set(nums[i], i);
  }
  
  return [];
}

// Test: twoSum([2, 7, 11, 15], 9)
// Expected: [0, 1]
// Your output: [1, 0]  ← Wrong order!

Trace to Find Bug:

Stepinums[i]complementseenseen.has(complement)?Return
1027{}False-
2---{2→0}-(Store 2→0)
3172{2→0}True[1, 0] ← BUG!

Bug Found: Line returns [i, seen.get(complement)!] = [1, 0], but expected [0, 1].

Fix: Return [seen.get(complement)!, i] instead.

Corrected Code:

typescript
return [seen.get(complement)!, i];  // Correct order

Manual tracing revealed:

  • Exactly which line caused wrong output
  • What the values were at that moment
  • The precise fix needed

This is more effective than guessing or adding random print statements.

Practical Preparation Strategies

Trace Before Running (Proactive Debugging)

Best practice: After writing code, trace it with a simple test case before hitting "Run."

Why:

  • Catches bugs immediately
  • Confirms your logic matches your intent
  • Saves time versus debug-run-debug cycles

Workflow:

  1. Write your solution
  2. Choose a simple test case (3-5 elements)
  3. Trace it fully on paper
  4. If trace matches expected output → Run code
  5. If trace diverges → Fix bug, trace again

Use Tracing to Understand Solutions

When learning from editorial solutions, trace them line-by-line.

How:

  1. Copy the solution code
  2. Create a trace table
  3. Execute with the provided example
  4. Watch how the algorithm manipulates data

Why: You internalize how the algorithm works, not just what it does. This builds deeper understanding than reading explanations, which relates to learning without memorization.

Simplify Test Cases for Tracing

LeetCode test cases can be large (arrays of 1000+ elements). Tracing these is impractical.

Strategy: Create minimal test cases that demonstrate the logic.

Example: For "Longest Substring Without Repeating Characters", instead of:

code
Input: "abcabcbb"

Use:

code
Input: "abca"  (small, still tests duplicate detection)

Rule: Test case should be just large enough to exercise the key logic, but small enough to trace fully.

Combine Tracing with Code Visualization Tools

After manual tracing, use tools like Python Tutor to verify your trace.

Workflow:

  1. Trace manually
  2. Run code in Python Tutor (or equivalent)
  3. Compare: Does the visualizer match your trace?
  4. If different → Find your tracing error or code bug

This validates your tracing skills while debugging.

Common Mistakes to Avoid

Mistake 1: Tracing in Your Head

You think through the execution mentally without writing anything down.

Result: You skip steps, make assumptions, miss the bug.

Fix: Always write the trace. Pen and paper or digital document—doesn't matter. Written tracing catches errors mental tracing misses.

Mistake 2: Tracing Too Fast

You try to trace quickly, updating multiple variables per row.

Result: Confusion about what changed when, missed intermediate states.

Fix: One meaningful change per row. If a line updates two variables, use two rows or clearly mark both changes.

Mistake 3: Not Tracing Edge Cases

You trace the "happy path" (normal input), miss edge cases (empty array, single element, null).

Fix: After tracing normal input, trace at least one edge case. This often reveals boundary bugs.

Mistake 4: Giving Up When Trace is Long

You start tracing a loop that runs 10 times. After 3 iterations, you stop because it's tedious.

Result: The bug was in iteration 7. You missed it.

Fix: For long loops, trace first 2-3 iterations fully, then spot-check later iterations. Or simplify test case to reduce iterations.

Mistake 5: Treating Tracing as a Last Resort

You only trace when completely stuck, after trying everything else.

Fix: Make tracing your first debugging step for logic errors. It's often faster than random print statements or blind guessing.

FAQ

How long should manual tracing take?
For a simple algorithm with a small test case, 5-10 minutes. For complex algorithms (DP, graph traversal), 15-20 minutes. It feels slow initially, but catches bugs faster than repeated run-debug cycles.

What should I practice before attempting manual tracing?
Be comfortable reading code and understanding basic operations (loops, conditionals, array access). If you can mentally execute x = x + 1, you can trace. Start with very simple code (5-10 lines) to build the skill.

Is this concept important for interviews?
Yes. Interviewers often ask "Can you walk me through your code with this example?" That's manual tracing. Being able to trace your own code fluently demonstrates understanding and helps you catch bugs during the interview.

Should I trace every problem I solve?
Not every problem, but trace when: (1) learning a new algorithm, (2) your code fails tests and you don't know why, (3) you want to verify correctness before running. As you improve, you'll trace mentally for simple code, on paper for complex code.

How do I trace recursive functions?
Track the call stack. Each recursive call gets its own row, with indentation to show depth. Write what parameters are passed, what gets returned, and how the return value is used. This reveals the recursive flow clearly.

Conclusion

Manual code tracing is the debugging technique that separates beginners who guess from those who systematically find and fix bugs.

Create a trace table with columns for variables and rows for execution steps. Execute your code line-by-line, updating values as the computer would. Track loop iterations, conditional branches, and function calls. Compare your traced output to expected output—when they diverge, you've found your bug.

Trace before running to catch bugs proactively. Trace after failures to pinpoint exactly where logic breaks. Trace complex algorithms to build deep understanding of how they manipulate data.

The key is writing everything down. Mental tracing skips steps and misses bugs. Paper or digital—doesn't matter. What matters is the systematic, line-by-line simulation that reveals what your code actually does, not what you think it does.

When working through problems, tools like LeetCopilot can guide you through execution traces and help visualize algorithm flow, complementing your manual tracing practice with interactive step-by-step breakdowns.

Every bug you find through tracing strengthens your mental model of how code executes. Eventually, you'll trace simple code automatically in your head and reserve paper tracing for complex algorithms. But that fluency starts with the discipline of writing down every step, tracking every variable, and seeing exactly where your logic leads. That's how you transform from hoping your code works to knowing it works.

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