Coming Soon

This lesson is currently being developed

More debugging tactics

Learn practical techniques for debugging C++ programs.

Debugging C++ Programs
Chapter
Beginner
Difficulty
40min
Estimated Time

What to Expect

Comprehensive explanations with practical examples

Interactive coding exercises to practice concepts

Knowledge quiz to test your understanding

Step-by-step guidance for beginners

Development Status

In Progress

Content is being carefully crafted to provide the best learning experience

Preview

Early Preview Content

This content is still being developed and may change before publication.

3.5 — More debugging tactics

In this lesson, you'll learn advanced debugging tactics that build upon the basics. These techniques are particularly useful for complex bugs that aren't easily found with simple print statements.

Using test-driven debugging

Test-driven debugging involves creating specific tests that reproduce the bug, then using those tests to verify your fix.

Creating targeted test cases

#include <iostream>
#include <vector>
#include <cassert>

// Bug: This function should remove all occurrences of a value
void removeValue(std::vector<int>& vec, int valueToRemove)
{
    // Original buggy implementation - only removes first occurrence
    /*
    for (auto it = vec.begin(); it != vec.end(); ++it)
    {
        if (*it == valueToRemove)
        {
            vec.erase(it);
            break;  // BUG: Only removes first occurrence
        }
    }
    */
    
    // Another buggy attempt - iterator invalidation
    /*
    for (auto it = vec.begin(); it != vec.end(); ++it)
    {
        if (*it == valueToRemove)
        {
            vec.erase(it);  // BUG: Invalidates iterator, causes undefined behavior
        }
    }
    */
    
    // Correct implementation
    auto it = vec.begin();
    while (it != vec.end())
    {
        if (*it == valueToRemove)
        {
            it = vec.erase(it);  // erase returns iterator to next element
        }
        else
        {
            ++it;
        }
    }
}

void printVector(const std::vector<int>& vec, const std::string& label)
{
    std::cout << label << ": ";
    for (int value : vec)
    {
        std::cout << value << " ";
    }
    std::cout << std::endl;
}

void testRemoveValue()
{
    std::cout << "=== Test-Driven Debugging for removeValue ===" << std::endl;
    
    // Test case 1: Remove single occurrence
    {
        std::vector<int> vec = {1, 2, 3, 4, 5};
        printVector(vec, "Before removing 3");
        removeValue(vec, 3);
        printVector(vec, "After removing 3");
        
        // Verify result
        std::vector<int> expected = {1, 2, 4, 5};
        assert(vec == expected && "Single occurrence removal failed");
        std::cout << "✓ Test 1 passed\n" << std::endl;
    }
    
    // Test case 2: Remove multiple occurrences (this would fail with buggy version)
    {
        std::vector<int> vec = {1, 2, 2, 3, 2, 4};
        printVector(vec, "Before removing all 2s");
        removeValue(vec, 2);
        printVector(vec, "After removing all 2s");
        
        // Verify result
        std::vector<int> expected = {1, 3, 4};
        assert(vec == expected && "Multiple occurrence removal failed");
        std::cout << "✓ Test 2 passed\n" << std::endl;
    }
    
    // Test case 3: Remove from empty vector
    {
        std::vector<int> vec = {};
        removeValue(vec, 5);
        assert(vec.empty() && "Empty vector test failed");
        std::cout << "✓ Test 3 passed\n" << std::endl;
    }
    
    // Test case 4: Remove value not in vector
    {
        std::vector<int> vec = {1, 3, 5, 7};
        std::vector<int> original = vec;  // Keep copy
        removeValue(vec, 2);
        assert(vec == original && "Remove non-existent value failed");
        std::cout << "✓ Test 4 passed\n" << std::endl;
    }
    
    // Test case 5: Remove all elements
    {
        std::vector<int> vec = {7, 7, 7};
        removeValue(vec, 7);
        assert(vec.empty() && "Remove all elements failed");
        std::cout << "✓ Test 5 passed\n" << std::endl;
    }
}

int main()
{
    testRemoveValue();
    std::cout << "All tests passed!" << std::endl;
    return 0;
}

Output:

=== Test-Driven Debugging for removeValue ===
Before removing 3: 1 2 3 4 5 
After removing 3: 1 2 4 5 
✓ Test 1 passed

Before removing all 2s: 1 2 2 3 2 4 
After removing all 2s: 1 3 4 
✓ Test 2 passed

✓ Test 3 passed

✓ Test 4 passed

✓ Test 5 passed

All tests passed!

Regression testing

Create tests that ensure fixes don't break existing functionality.

Building a regression test suite

#include <iostream>
#include <cmath>
#include <iomanip>

// Mathematical functions that need careful testing
class MathUtils
{
public:
    // Bug history: This had an off-by-one error in factorial calculation
    static long long factorial(int n)
    {
        if (n < 0) return -1;  // Error indicator
        if (n <= 1) return 1;
        
        long long result = 1;
        for (int i = 2; i <= n; ++i)  // Fixed: was i < n (off-by-one)
        {
            result *= i;
        }
        return result;
    }
    
    // Bug history: This didn't handle negative bases correctly
    static double power(double base, int exponent)
    {
        if (exponent == 0) return 1.0;
        if (exponent == 1) return base;
        
        bool negativeExponent = (exponent < 0);
        int absExponent = std::abs(exponent);
        
        double result = 1.0;
        for (int i = 0; i < absExponent; ++i)
        {
            result *= base;
        }
        
        return negativeExponent ? (1.0 / result) : result;
    }
    
    // Bug history: Had precision issues with floating-point comparison
    static bool isPerfectSquare(double number)
    {
        if (number < 0) return false;
        
        double root = std::sqrt(number);
        double rounded = std::round(root);
        
        // Fixed: Use epsilon for floating-point comparison
        const double epsilon = 1e-10;
        return std::abs(root - rounded) < epsilon;
    }
};

class RegressionTests
{
private:
    static int testsPassed;
    static int testsTotal;
    
public:
    static void assertEqual(long long actual, long long expected, const std::string& testName)
    {
        testsTotal++;
        if (actual == expected)
        {
            std::cout << "✓ " << testName << " passed" << std::endl;
            testsPassed++;
        }
        else
        {
            std::cout << "✗ " << testName << " FAILED: expected " << expected 
                      << ", got " << actual << std::endl;
        }
    }
    
    static void assertEqual(double actual, double expected, const std::string& testName, double tolerance = 1e-10)
    {
        testsTotal++;
        if (std::abs(actual - expected) < tolerance)
        {
            std::cout << "✓ " << testName << " passed" << std::endl;
            testsPassed++;
        }
        else
        {
            std::cout << "✗ " << testName << " FAILED: expected " << std::fixed << std::setprecision(10)
                      << expected << ", got " << actual << std::endl;
        }
    }
    
    static void assertEqual(bool actual, bool expected, const std::string& testName)
    {
        testsTotal++;
        if (actual == expected)
        {
            std::cout << "✓ " << testName << " passed" << std::endl;
            testsPassed++;
        }
        else
        {
            std::cout << "✗ " << testName << " FAILED: expected " << std::boolalpha 
                      << expected << ", got " << actual << std::endl;
        }
    }
    
    static void runAllTests()
    {
        testsPassed = 0;
        testsTotal = 0;
        
        std::cout << "=== Regression Test Suite ===" << std::endl;
        
        testFactorial();
        testPower();
        testIsPerfectSquare();
        
        std::cout << "\n=== Test Results ===" << std::endl;
        std::cout << "Tests passed: " << testsPassed << "/" << testsTotal << std::endl;
        
        if (testsPassed == testsTotal)
        {
            std::cout << "🎉 All regression tests passed!" << std::endl;
        }
        else
        {
            std::cout << "❌ Some tests failed - regression detected!" << std::endl;
        }
    }
    
private:
    static void testFactorial()
    {
        std::cout << "\nTesting factorial function:" << std::endl;
        
        // Basic cases
        assertEqual(MathUtils::factorial(0), 1LL, "factorial(0)");
        assertEqual(MathUtils::factorial(1), 1LL, "factorial(1)");
        assertEqual(MathUtils::factorial(5), 120LL, "factorial(5)");
        
        // Edge cases
        assertEqual(MathUtils::factorial(-1), -1LL, "factorial(-1) error handling");
        
        // Regression test: This would fail if off-by-one bug returned
        assertEqual(MathUtils::factorial(4), 24LL, "factorial(4) regression test");
    }
    
    static void testPower()
    {
        std::cout << "\nTesting power function:" << std::endl;
        
        // Basic cases
        assertEqual(MathUtils::power(2.0, 3), 8.0, "power(2, 3)");
        assertEqual(MathUtils::power(5.0, 0), 1.0, "power(5, 0)");
        assertEqual(MathUtils::power(3.0, 1), 3.0, "power(3, 1)");
        
        // Negative base
        assertEqual(MathUtils::power(-2.0, 2), 4.0, "power(-2, 2)");
        assertEqual(MathUtils::power(-2.0, 3), -8.0, "power(-2, 3)");
        
        // Negative exponent
        assertEqual(MathUtils::power(2.0, -2), 0.25, "power(2, -2)");
    }
    
    static void testIsPerfectSquare()
    {
        std::cout << "\nTesting isPerfectSquare function:" << std::endl;
        
        // Perfect squares
        assertEqual(MathUtils::isPerfectSquare(4.0), true, "isPerfectSquare(4)");
        assertEqual(MathUtils::isPerfectSquare(9.0), true, "isPerfectSquare(9)");
        assertEqual(MathUtils::isPerfectSquare(16.0), true, "isPerfectSquare(16)");
        
        // Non-perfect squares
        assertEqual(MathUtils::isPerfectSquare(5.0), false, "isPerfectSquare(5)");
        assertEqual(MathUtils::isPerfectSquare(10.0), false, "isPerfectSquare(10)");
        
        // Edge cases
        assertEqual(MathUtils::isPerfectSquare(0.0), true, "isPerfectSquare(0)");
        assertEqual(MathUtils::isPerfectSquare(-1.0), false, "isPerfectSquare(-1)");
        
        // Regression test: Floating-point precision issues
        assertEqual(MathUtils::isPerfectSquare(1.0), true, "isPerfectSquare(1.0) precision test");
    }
};

int RegressionTests::testsPassed = 0;
int RegressionTests::testsTotal = 0;

int main()
{
    RegressionTests::runAllTests();
    return 0;
}

Code inspection techniques

Systematic code review can catch bugs that testing might miss.

Walkthrough debugging

#include <iostream>
#include <vector>

// Function with subtle bugs - let's walk through it step by step
int findKthLargest(std::vector<int>& nums, int k)
{
    std::cout << "=== Code Walkthrough: findKthLargest ===" << std::endl;
    std::cout << "Input: nums = [";
    for (size_t i = 0; i < nums.size(); ++i)
    {
        std::cout << nums[i];
        if (i < nums.size() - 1) std::cout << ", ";
    }
    std::cout << "], k = " << k << std::endl;
    
    // Step 1: Input validation (missing in original buggy version)
    std::cout << "\nStep 1: Input validation" << std::endl;
    if (k <= 0 || k > static_cast<int>(nums.size()))
    {
        std::cout << "❌ Invalid k value: " << k << std::endl;
        return -1;  // Error indicator
    }
    std::cout << "✓ k is valid" << std::endl;
    
    // Step 2: Sort the array (simple but inefficient approach)
    std::cout << "\nStep 2: Sorting array" << std::endl;
    
    // Simple bubble sort for educational purposes (inefficient but clear)
    for (size_t i = 0; i < nums.size(); ++i)
    {
        for (size_t j = 0; j < nums.size() - 1 - i; ++j)
        {
            if (nums[j] < nums[j + 1])  // Sort in descending order
            {
                std::swap(nums[j], nums[j + 1]);
            }
        }
    }
    
    std::cout << "Sorted array (descending): [";
    for (size_t i = 0; i < nums.size(); ++i)
    {
        std::cout << nums[i];
        if (i < nums.size() - 1) std::cout << ", ";
    }
    std::cout << "]" << std::endl;
    
    // Step 3: Return kth element (watch out for off-by-one!)
    std::cout << "\nStep 3: Finding kth largest" << std::endl;
    std::cout << "k = " << k << ", so we want index " << (k - 1) << std::endl;
    
    int result = nums[k - 1];  // k-1 because arrays are 0-indexed
    std::cout << "The " << k << "th largest element is: " << result << std::endl;
    
    return result;
}

// Version with common bugs for comparison
int findKthLargest_buggy(std::vector<int>& nums, int k)
{
    // Bug 1: No input validation
    // Bug 2: Wrong index calculation
    // Bug 3: Assumes sorted in ascending order
    
    // Sort in ascending order (wrong for this purpose)
    for (size_t i = 0; i < nums.size(); ++i)
    {
        for (size_t j = 0; j < nums.size() - 1 - i; ++j)
        {
            if (nums[j] > nums[j + 1])
            {
                std::swap(nums[j], nums[j + 1]);
            }
        }
    }
    
    return nums[k];  // Bug: should be k-1, and wrong end of array
}

void demonstrateWalkthrough()
{
    std::cout << "=== Walkthrough Debugging Demo ===" << std::endl;
    
    std::vector<int> test1 = {3, 2, 1, 5, 6, 4};
    std::vector<int> test1_copy = test1;  // Keep original for buggy version
    
    std::cout << "\n--- Correct version walkthrough ---" << std::endl;
    int result1 = findKthLargest(test1, 2);
    
    std::cout << "\n--- Buggy version comparison ---" << std::endl;
    std::cout << "Buggy version result: " << findKthLargest_buggy(test1_copy, 2) << std::endl;
    std::cout << "Expected: 5 (2nd largest)" << std::endl;
}

int main()
{
    demonstrateWalkthrough();
    return 0;
}

Output:

=== Walkthrough Debugging Demo ===

--- Correct version walkthrough ---
=== Code Walkthrough: findKthLargest ===
Input: nums = [3, 2, 1, 5, 6, 4], k = 2

Step 1: Input validation
✓ k is valid

Step 2: Sorting array
Sorted array (descending): [6, 5, 4, 3, 2, 1]

Step 3: Finding kth largest
k = 2, so we want index 1
The 2th largest element is: 5

--- Buggy version comparison ---
Buggy version result: 4
Expected: 5 (2nd largest)

Hypothesis-driven debugging

Form and test specific hypotheses about what might be causing the bug.

Systematic hypothesis testing

#include <iostream>
#include <string>
#include <cctype>

// Bug: Password validation function sometimes accepts invalid passwords
bool isValidPassword(const std::string& password)
{
    // Requirements:
    // 1. At least 8 characters long
    // 2. At least one uppercase letter
    // 3. At least one lowercase letter  
    // 4. At least one digit
    // 5. No spaces allowed
    
    // Hypothesis 1: Length check might be wrong
    if (password.length() < 8)
    {
        return false;
    }
    
    bool hasUpper = false;
    bool hasLower = false;
    bool hasDigit = false;
    
    for (char c : password)
    {
        // Hypothesis 2: Space check might be missing or wrong
        if (c == ' ')
        {
            return false;  // No spaces allowed
        }
        
        // Hypothesis 3: Character classification might be incorrect
        if (std::isupper(c))
        {
            hasUpper = true;
        }
        else if (std::islower(c))
        {
            hasLower = true;
        }
        else if (std::isdigit(c))
        {
            hasDigit = true;
        }
        
        // Early exit optimization
        if (hasUpper && hasLower && hasDigit)
        {
            break;  // All requirements met
        }
    }
    
    return hasUpper && hasLower && hasDigit;
}

void testHypotheses()
{
    std::cout << "=== Hypothesis-Driven Debugging ===" << std::endl;
    
    struct TestCase
    {
        std::string password;
        bool expected;
        std::string hypothesis;
    };
    
    TestCase tests[] = {
        // Test Hypothesis 1: Length requirements
        {"Abc123", false, "Length < 8 should be invalid"},
        {"Abcdef12", true, "Length >= 8 with all requirements should be valid"},
        
        // Test Hypothesis 2: Space handling
        {"Abc 123def", false, "Spaces should make password invalid"},
        {"Abc123def", true, "No spaces should be valid"},
        
        // Test Hypothesis 3: Character classification
        {"abcdefgh", false, "Only lowercase should be invalid"},
        {"ABCDEFGH", false, "Only uppercase should be invalid"},
        {"AbcdefGH", false, "No digits should be invalid"},
        {"12345678", false, "Only digits should be invalid"},
        {"Abc12345", true, "Mix of upper, lower, digits should be valid"},
        
        // Edge cases to test additional hypotheses
        {"", false, "Empty string should be invalid"},
        {"A1a", false, "Too short even with all char types"},
        {"Aa1!@#$%", true, "Special characters should be allowed"},
    };
    
    std::cout << "\nTesting each hypothesis:" << std::endl;
    
    for (const auto& test : tests)
    {
        bool result = isValidPassword(test.password);
        std::string status = (result == test.expected) ? "✓ PASS" : "✗ FAIL";
        
        std::cout << status << " \"" << test.password << "\" -> " 
                  << std::boolalpha << result 
                  << " (" << test.hypothesis << ")" << std::endl;
        
        if (result != test.expected)
        {
            std::cout << "   Expected: " << test.expected 
                      << ", Got: " << result << std::endl;
        }
    }
}

// Alternative implementation to test different hypothesis
bool isValidPassword_v2(const std::string& password)
{
    // Hypothesis: Original might have issues with edge cases
    
    if (password.empty() || password.length() < 8)
    {
        return false;
    }
    
    int upperCount = 0, lowerCount = 0, digitCount = 0;
    
    for (char c : password)
    {
        if (c == ' ') return false;  // No spaces
        
        if (c >= 'A' && c <= 'Z') upperCount++;
        else if (c >= 'a' && c <= 'z') lowerCount++;
        else if (c >= '0' && c <= '9') digitCount++;
    }
    
    return upperCount > 0 && lowerCount > 0 && digitCount > 0;
}

void compareImplementations()
{
    std::cout << "\n=== Comparing Implementations ===" << std::endl;
    
    std::string testPasswords[] = {
        "Password123",
        "password123", 
        "PASSWORD123",
        "Password",
        "Pass 123",
        "Pássword123"  // Test with non-ASCII character
    };
    
    for (const std::string& pwd : testPasswords)
    {
        bool result1 = isValidPassword(pwd);
        bool result2 = isValidPassword_v2(pwd);
        
        std::cout << "\"" << pwd << "\":" << std::endl;
        std::cout << "  Version 1 (std::is* functions): " << std::boolalpha << result1 << std::endl;
        std::cout << "  Version 2 (manual ranges):      " << std::boolalpha << result2 << std::endl;
        
        if (result1 != result2)
        {
            std::cout << "  ⚠️  Implementations disagree!" << std::endl;
        }
        std::cout << std::endl;
    }
}

int main()
{
    testHypotheses();
    compareImplementations();
    return 0;
}

Rubber duck debugging enhanced

A more structured approach to explaining your code to find bugs.

Structured self-explanation

#include <iostream>
#include <vector>

// Bug: This function should merge two sorted arrays
std::vector<int> mergeSortedArrays(const std::vector<int>& arr1, const std::vector<int>& arr2)
{
    std::cout << "=== Rubber Duck Debugging Session ===" << std::endl;
    std::cout << "Explaining mergeSortedArrays function step by step..." << std::endl;
    
    std::vector<int> result;
    size_t i = 0, j = 0;
    
    std::cout << "\n🦆 Duck: What are we trying to do?" << std::endl;
    std::cout << "Me: Merge two sorted arrays into one sorted array." << std::endl;
    
    std::cout << "\n🦆 Duck: How do we approach this?" << std::endl;
    std::cout << "Me: Use two pointers, one for each array, and compare elements." << std::endl;
    
    std::cout << "\n🦆 Duck: Let's trace through the main loop..." << std::endl;
    
    while (i < arr1.size() && j < arr2.size())
    {
        std::cout << "Comparing arr1[" << i << "]=" << arr1[i] 
                  << " with arr2[" << j << "]=" << arr2[j] << std::endl;
        
        if (arr1[i] <= arr2[j])
        {
            result.push_back(arr1[i]);
            std::cout << "Added " << arr1[i] << " from arr1" << std::endl;
            i++;
        }
        else
        {
            result.push_back(arr2[j]);
            std::cout << "Added " << arr2[j] << " from arr2" << std::endl;
            j++;
        }
    }
    
    std::cout << "\n🦆 Duck: What happens when one array is exhausted?" << std::endl;
    std::cout << "Me: We need to add remaining elements from the other array." << std::endl;
    
    // Add remaining elements from arr1
    while (i < arr1.size())
    {
        result.push_back(arr1[i]);
        std::cout << "Added remaining " << arr1[i] << " from arr1" << std::endl;
        i++;
    }
    
    // Add remaining elements from arr2
    while (j < arr2.size())
    {
        result.push_back(arr2[j]);
        std::cout << "Added remaining " << arr2[j] << " from arr2" << std::endl;
        j++;
    }
    
    std::cout << "\n🦆 Duck: Does this handle all edge cases?" << std::endl;
    std::cout << "Me: Let me think... empty arrays, arrays of different sizes..." << std::endl;
    std::cout << "Me: Yes, the while loops handle these cases correctly." << std::endl;
    
    return result;
}

void rubberDuckSession()
{
    std::vector<int> arr1 = {1, 3, 5, 7};
    std::vector<int> arr2 = {2, 4, 6, 8, 9, 10};
    
    std::cout << "Input arrays:" << std::endl;
    std::cout << "arr1: ";
    for (int x : arr1) std::cout << x << " ";
    std::cout << "\narr2: ";
    for (int x : arr2) std::cout << x << " ";
    std::cout << std::endl;
    
    std::vector<int> merged = mergeSortedArrays(arr1, arr2);
    
    std::cout << "\nResult: ";
    for (int x : merged) std::cout << x << " ";
    std::cout << std::endl;
    
    std::cout << "\n🦆 Duck: Is the result correct?" << std::endl;
    std::cout << "Me: Let me verify... 1,2,3,4,5,6,7,8,9,10 - yes, that's correct!" << std::endl;
}

int main()
{
    rubberDuckSession();
    return 0;
}

Performance debugging

Sometimes bugs manifest as performance problems rather than incorrect output.

Identifying performance bottlenecks

#include <iostream>
#include <vector>
#include <chrono>
#include <algorithm>

class PerformanceDebugger
{
public:
    // Bug: Inefficient algorithm causing performance issues
    static bool containsDuplicate_slow(const std::vector<int>& nums)
    {
        // O(n²) approach - performance bug for large inputs
        for (size_t i = 0; i < nums.size(); ++i)
        {
            for (size_t j = i + 1; j < nums.size(); ++j)
            {
                if (nums[i] == nums[j])
                {
                    return true;
                }
            }
        }
        return false;
    }
    
    // Fixed version with better performance
    static bool containsDuplicate_fast(const std::vector<int>& nums)
    {
        // O(n log n) approach using sorting
        std::vector<int> sorted_nums = nums;
        std::sort(sorted_nums.begin(), sorted_nums.end());
        
        for (size_t i = 1; i < sorted_nums.size(); ++i)
        {
            if (sorted_nums[i] == sorted_nums[i-1])
            {
                return true;
            }
        }
        return false;
    }
    
    static void measurePerformance()
    {
        std::cout << "=== Performance Debugging ===" << std::endl;
        
        // Test with different input sizes
        std::vector<int> sizes = {100, 1000, 5000, 10000};
        
        for (int size : sizes)
        {
            // Create test data
            std::vector<int> testData;
            for (int i = 0; i < size; ++i)
            {
                testData.push_back(i);
            }
            testData.push_back(size / 2);  // Add one duplicate
            
            std::cout << "\nTesting with " << size << " elements:" << std::endl;
            
            // Measure slow version
            auto start = std::chrono::high_resolution_clock::now();
            bool result1 = containsDuplicate_slow(testData);
            auto end = std::chrono::high_resolution_clock::now();
            
            auto duration1 = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
            
            // Measure fast version
            start = std::chrono::high_resolution_clock::now();
            bool result2 = containsDuplicate_fast(testData);
            end = std::chrono::high_resolution_clock::now();
            
            auto duration2 = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
            
            std::cout << "Slow version: " << duration1.count() << " μs (result: " 
                      << std::boolalpha << result1 << ")" << std::endl;
            std::cout << "Fast version: " << duration2.count() << " μs (result: " 
                      << std::boolalpha << result2 << ")" << std::endl;
            
            if (duration1.count() > 0)
            {
                double speedup = static_cast<double>(duration1.count()) / duration2.count();
                std::cout << "Speedup: " << speedup << "x faster" << std::endl;
            }
        }
    }
};

int main()
{
    PerformanceDebugger::measurePerformance();
    return 0;
}

Summary

Advanced debugging tactics expand your problem-solving toolkit:

  1. Test-driven debugging: Create targeted tests to isolate and verify bug fixes
  2. Regression testing: Ensure fixes don't break existing functionality
  3. Code inspection: Systematic review and walkthrough of code logic
  4. Hypothesis-driven debugging: Form and test specific theories about bugs
  5. Enhanced rubber duck debugging: Structured self-explanation process
  6. Performance debugging: Identify and fix performance-related bugs

These techniques are particularly valuable for complex bugs that simple print debugging can't solve. They require more time and effort but can catch subtle issues that might otherwise go unnoticed.

In the next lesson, you'll learn how to use integrated debugger tools that provide even more powerful debugging capabilities.

Quiz

  1. What is the main advantage of test-driven debugging?
  2. Why are regression tests important after fixing a bug?
  3. How does hypothesis-driven debugging differ from random trial-and-error?
  4. When might performance debugging be necessary even if your program produces correct output?
  5. What makes structured rubber duck debugging more effective than just thinking through the problem?

Practice exercises

  1. Create a test suite for this function that would catch the bug:

    int binarySearch(const std::vector<int>& arr, int target)
    {
        int left = 0, right = arr.size() - 1;
        while (left <= right)
        {
            int mid = (left + right) / 2;
            if (arr[mid] == target) return mid;
            else if (arr[mid] < target) left = mid + 1;
            else right = mid - 1;
        }
        return -1;
    }
    
  2. Write a performance test to compare these two implementations:

    // Version A
    std::string reverseString_A(const std::string& str)
    {
        std::string result = "";
        for (int i = str.length() - 1; i >= 0; --i)
        {
            result += str[i];
        }
        return result;
    }
    
    // Version B
    std::string reverseString_B(const std::string& str)
    {
        std::string result = str;
        std::reverse(result.begin(), result.end());
        return result;
    }
    
  3. Practice hypothesis-driven debugging: Given a sorting function that sometimes produces incorrect results, what hypotheses would you test and in what order?

  4. Create regression tests for a calculator class that has had bugs in the past with division by zero, integer overflow, and floating-point precision.

Continue Learning

Explore other available lessons while this one is being prepared.

View Course

Explore More Courses

Discover other available courses while this lesson is being prepared.

Browse Courses

Lesson Discussion

Share your thoughts and questions

💬

No comments yet. Be the first to share your thoughts!

Sign in to join the discussion