Create your own
Lesson illustration

Debugging and Verifying TypeScript Code

Welcome back. In our previous lesson, we established a clear framework for choosing between backtracking and dynamic programming when tackling combinatorial problems. This ability to select the right high-level strategy is a cornerstone of effective problem-solving.

Now, we shift our focus to an equally critical, and often more common, interview scenario: what happens when a solution doesn't work? Whether you're debugging your own code under pressure or are explicitly asked to fix a flawed implementation, the ability to systematically find and repair errors is a skill that separates senior engineers from junior ones.

Today's lesson addresses this directly. We will learn how to repair a flawed TypeScript solution by locating modeling, algorithmic, complexity, numeric, or data-structure errors, and then verifying the fix with edge cases. Instead of guessing or making random changes, you will learn to approach debugging as a structured, logical process, turning what can be a source of anxiety into a demonstration of your analytical strength.

The Debugging Mindset: From "It's Broken" to "I See Why"

As an experienced developer, you know that debugging is a core part of the job. In an algorithmic context, the bugs are rarely simple syntax errors. They are usually subtle flaws in logic, assumptions, or understanding. The goal isn't just to make the code pass; it's to understand why it failed and prove that the fix is correct and robust.

A systematic approach is your best tool. The videos from Anthony D. Mays and Codebagel both emphasize that successful problem-solving (and by extension, debugging) begins long before you write or change code.

How to Solve ANY Coding Interview Question in 6 Steps

Watch this video from Anthony D. Mays for a refresher on the first principles of dissecting a problem.

Pay close attention to the first three steps he outlines: repeating the question, asking clarifying questions, and working through examples. When debugging, these steps help you re-establish a "ground truth" for what the code should be doing.

This initial process gives you the two things you need to start debugging:

  1. A clear understanding of the intended behavior.
  2. A set of test cases, including edge cases, that the correct solution must pass.

A Taxonomy of Algorithmic Bugs

Bugs in algorithmic code tend to fall into predictable categories. Recognizing these patterns helps you quickly zero in on the likely source of an error. The article "JavaScript Interview Questions & Tips for Senior Engineers" from Interviewing.io provides excellent, practical examples of these pitfalls in a JavaScript/TypeScript context.

JavaScript Interview Questions & Tips for Senior Engineers

This article breaks down several common mistakes made in JavaScript interviews. We will use it to build a mental checklist of bug categories.

Focus on the section titled Common Mistakes. As you read, pay attention to the type of error in each example: Modeling/Language-Specific Error: Read about the improper use of 'this'. This is a classic error where the mental model of the language's execution context is wrong. Complexity Error: In the section Using Array as a Queue, note how a seemingly correct implementation of BFS has a hidden performance bug due to Array.prototype.shift(). The algorithm works, but it's too slow. Data-Structure / State Error: The example of a flawed DFS under Unintentionally Mutating shows an error in state management, where a visited array is shared across different calls. Numeric/API Error: The section on the sort() method highlights a bug from misunderstanding a built-in function's default behavior.

Beyond these, a blog post by Anthony D. Mays points out another critical category.

How to Practice LeetCode Problems (The Right Way) - Anthony D. Mays

This resource provides a checklist for testing your own code.

In section 9, "Test your code," find the bulleted list under the mental checklist. This list includes classic Algorithmic Errors like off-by-one errors and reversed conditionals (< instead of >=). These are pure logic bugs, independent of language features.

Here is a summary of our bug taxonomy:

  • Modeling Errors: The translation from the problem to code is wrong (e.g., misunderstanding this, closures, or pass-by-reference).
  • Complexity Errors: The algorithm is functionally correct but does not meet the performance requirements (e.g., using an O(n) operation inside a loop, resulting in O(n²)).
  • Data-Structure/State Errors: Using the wrong data structure, or, more commonly, managing state incorrectly across function calls or iterations (e.g., failing to reset a variable, mutating a shared object).
  • Algorithmic Errors: The logic itself is flawed (e.g., off-by-one errors, incorrect loop bounds, wrong conditional checks).
  • Numeric/API Errors: Incorrectly using a built-in function or misunderstanding numeric precision (Array.sort() on numbers, floating-point issues).

The Debugging Process in Action

When your code fails, resist the urge to change code randomly. Follow a process. The Codebagel video offers a fantastic, practical guide to debugging an implementation error.

How to Solve ANY LeetCode Problem (Step-by-Step)

This video provides a step-by-step guide to solving LeetCode problems. We're interested in what to do when things go wrong.

Watch the final section on debugging, starting from "If your code runs, but tests fail". The key techniques are: Identify if the failure is an edge case or a core logic issue. Manually walk a failing test case through your code, line by line. Use print/log statements to check variable states at different points and find where they diverge from your expectation.

Let's combine these ideas into a single, repeatable process.

A 4-Step Debugging Workflow

Imagine you are given this flawed TypeScript function. The goal is to find the length of the longest substring without repeating characters.

// Problem: Longest Substring Without Repeating Characters (LeetCode 3)
// Flawed Implementation
function lengthOfLongestSubstring(s: string): number {
    let maxLength = 0;
    for (let i = 0; i < s.length; i++) {
        const currentChars = new Set<string>();
        for (let j = i; j < s.length; j++) {
            currentChars.add(s[j]);
            maxLength = Math.max(maxLength, currentChars.size);
            if (currentChars.has(s[j])) {
                break;
            }
        }
    }
    return maxLength;
}

This code looks plausible but contains a subtle bug. Let's apply our workflow.

1. Understand Intent & Find a Failing Case:
The intent is to find the longest substring with unique characters. Let's test with s = "pwwkew".

  • "p" -> 1
  • "w" -> 1
  • "wk" -> 2
  • "kew" -> 3
    The correct answer is 3 (for "wke" or "kew"). Let's trace our code with this input.

2. Manual Walkthrough:

  • i = 0 (p):
    • j = 0: currentChars becomes {'p'}. maxLength is 1. currentChars.has('p') is true. break. (Incorrect logic here!)
  • i = 1 (w):
    • j = 1: currentChars becomes {'w'}. maxLength is 1. currentChars.has('w') is true. break.
      ...and so on.

Let's refine the trace on the first iteration (i = 0, s = "pwwkew"):

  • j = 0 (char 'p'): currentChars.add('p'). Set is now {'p'}. maxLength becomes max(0, 1) = 1. Then, we check if (currentChars.has('p')). It does. We break. The inner loop finishes.
  • i = 1 (char 'w'):
  • j = 1 (char 'w'): currentChars.add('w'). Set is {'w'}. maxLength becomes max(1, 1) = 1. if (currentChars.has('w')). It does. We break.

3. Isolate and Categorize the Flaw:
The problem is immediately obvious. The code adds the character to the set before checking if it's already there. This means the if condition for a duplicate is met on the very first character of any new substring, causing the inner loop to terminate prematurely.

This is a classic Algorithmic Error. The order of operations is wrong.

4. Propose and Verify a Fix:
The fix is to check for the character before adding it.

// Corrected Implementation
function lengthOfLongestSubstring(s: string): number {
    let maxLength = 0;
    for (let i = 0; i < s.length; i++) {
        const currentChars = new Set<string>();
        for (let j = i; j < s.length; j++) {
            if (currentChars.has(s[j])) { // Check first
                break;
            }
            currentChars.add(s[j]); // Add second
            maxLength = Math.max(maxLength, currentChars.size);
        }
    }
    return maxLength;
}

(Note: This is a correct O(n²) brute-force solution. The optimal solution uses a sliding window, but for demonstrating debugging, this is sufficient).

Now, let's verify with our failing case s = "pwwkew" and some edge cases.

  • s = "pwwkew":
    • i = 0: j=0 ('p'), adds 'p'. maxLength=1. j=1 ('w'), has('w') is false. adds 'w'. maxLength=2. j=2 ('w'), has('w') is true. break. maxLength is 2.
    • i = 1: ...
    • i = 2 (w):
      • j=2 ('w'), adds 'w'. maxLength is max(2, 1) = 2.
      • j=3 ('k'), adds 'k'. maxLength is max(2, 2) = 2.
      • j=4 ('e'), adds 'e'. maxLength is max(2, 3) = 3.
      • j=5 ('w'), has('w') is true. break.
    • The final result will be 3. Correct.
  • Edge Case: s = "": Outer loop doesn't run. maxLength remains 0. Correct.
  • Edge Case: s = "bbbbb": maxLength will be 1. Correct.
  • Edge Case: s = "abcdef": maxLength will be 6. Correct.

The fix is verified.

Key Takeaways

You've now reached the stage where you can not only devise solutions but also systematically dismantle and repair them. This is a profound leap in skill.

  • Debugging is a Process, Not a Panic: When code fails, fall back on a structured workflow: understand intent, find a failing case, trace manually, isolate the error, categorize it, and then fix and verify.
  • Know Your Enemy: Bugs fall into common categories. Having a mental taxonomy (Modeling, Complexity, Data-Structure, Algorithmic, API) helps you diagnose problems faster. Your deep experience with JavaScript makes you particularly well-suited to spot language-specific modeling errors.
  • The Manual Trace is Your Superpower: Stepping through code with a concrete example, tracking variable states on paper or in your head, is the single most effective way to find logic errors. Resist the urge to rely on a debugger until you've tried this first.

In our next lesson, we will put everything together. You will complete a full mock interview, where you'll be expected to clarify, implement, test, and explain a solution to a Medium problem within a time limit. The debugging skills you learned today will be your safety net, allowing you to find and fix your own mistakes confidently.

Can't find a good explanation? Sign up and we'll make it for you

Sign up