3.3 — A strategy for debugging

When debugging a program, in most cases the vast majority of your time will be spent trying to find where the error actually is. Once the issue is found, the remaining steps (fixing the issue and validating that the issue was fixed) are often trivial in comparison.

In this lesson, we’ll start exploring how to find errors.

Finding problems via code inspection

Let’s say you’ve noticed a problem, and you want to track the cause of that specific problem down. In many cases (especially in smaller programs), we can quickly home in on the proximity of where the issue is.

Consider the following program snippet:

If you expected this program to print the names in alphabetical order, but it printed them in opposite order instead, the problem is probably in the sortNames function. In cases where you can narrow the problem down to a specific function, you may be able to spot the issue just by looking at the code.

However, as programs get more complex, finding issues by code inspection becomes more complex as well.

First, there’s a lot more code to look at. Looking at every line of code in a program that is thousands of lines long can take a really long time (not to mention it’s incredibly boring). Second, the code itself tends to be more complex, with more possible places for things to go wrong. Third, the code’s behavior may not give you many clues as to where things are going wrong. If you wrote a program to output stock recommendations and it actually output nothing at all, you probably wouldn’t have much of a lead on where to start looking for the problem.

Finally, bugs can be caused by making bad assumptions. It’s almost impossible to visually spot a bug caused by a bad assumption, because you’re likely to make the same bad assumption when inspecting the code, and not notice the error. So if we have an issue that we can’t find via code inspection, how do we find it?

Finding problems by running the program

Fortunately, if we can’t find an issue via code inspection, there is another avenue we can take: we can watch the behavior of the program as it runs, and try to diagnose the issue from that. This approach can be generalized as:

  1. Figure out how to reproduce the problem
  2. Run the program and gather information to narrow down where the problem is
  3. Repeat the prior step until you find the problem

For the rest of this chapter, we’ll discuss techniques to facilitate this approach.

Reproducing the problem

The first and most important step in finding a problem is to be able to reproduce the problem. The reason is simple: it’s extremely hard to find an issue unless you can observe it occurring.

Back to our ice dispenser analogy -- let’s say one day your friend tells you that your ice dispenser isn’t working. You go to look at it, and it works fine. How would you diagnose the problem? It would be very difficult. However, if you could actually see the issue of the ice dispenser not working, then you could start to diagnose why it wasn’t working much more effectively.

If a software issue is blatant (e.g. the program crashes in the same place every time you run it) then reproducing the problem can be trivial. However, sometimes reproducing an issue can be a lot more difficult. The problem may only occur on certain computers, or in particular circumstances (e.g. when the user enters certain input). In such cases, generating a set of reproduction steps can be helpful. Reproduction steps are a list of clear and precise steps that can be followed to cause an issue to recur with a high level of predictability. The goal is to be able to cause the issue to reoccur as much as possible, so we can run our program over and over and look for clues to determine what’s causing the problem. If the issue can be reproduced 100% of the time, that’s ideal, but less than 100% reproducibility can be okay. An issue that occurs only 50% of the time simply means it’ll take twice as long to diagnose the issue, as half the time the program won’t exhibit the problem and thus not contribute any useful diagnostic information.

Homing in on issues

Once we can reasonably reproduce the problem, the next step is to figure out where in the code the problem is. Based on the nature of the problem, this may be easy or difficult. For the sake of example, let’s say we don’t have much of an idea where the problem actually is. How do we find it?

An analogy will serve us well here. Let’s play a game of hi-lo. I’m going to ask you to guess a number between 1 and 10. For each guess you make, I’ll tell you whether each guess is too high, too low, or correct. An instance of this game might look like this:

You: 5
Me: Too low
You: 8
Me: Too high
You: 6
Me: Too low
You: 7
Me: Correct

In the above game, you don’t have to guess every number to find the number I was thinking of. Through the process of making guesses and considering the information you learn from each guess, you can “home in” on the correct number with only a few guesses (if you use an optimal strategy, you can always find the number I’m thinking of in 4 or fewer guesses).

We can use a similar process to debug programs. In the worst case, we may have no idea where the bug is. However, we do know that the problem must be somewhere in the code that executes between the beginning of the program and the point where the program exhibits the first incorrect symptom that we can observe. That at least rules out the parts of the program that execute after the first observable symptom. But that still potentially leaves a lot of code to cover. To diagnose the issue, we’ll make some educated guesses about where the problem is, with the goal of homing in on the problem quickly.

Often, whatever it was that caused us to notice the problem will give us an initial guess that’s close to where the actual problem is. For example, if the program isn’t writing data to a file when it should be, then the issue is probably somewhere in the code that handles writing to a file (duh!). Then we can use a hi-lo like strategy to try and isolate where the problem actually is.

For example:

  • If at some point in our program, we can prove that the problem has not occurred yet, this is analogous to receiving a “too low” hi-lo result -- we know the problem must be somewhere later in the program. For example, if our program is crashing in the same place every time, and we can prove that the program has not crashed at a particular point in the execution of the program, then the crash must be later in the code.
  • If at some point in our program we can observe incorrect behavior related to the problem, then this is analogous to receiving a “too high” hi-lo result, and we know the problem must be somewhere earlier in the program. For example, let’s say a program prints the value of some variable x. You were expecting it to print value 2, but it printed 8 instead. Variable x must have the wrong value. If, at some point during the execution of our program, we can see that variable x already has value 8, then we know the problem must have occurred before that point.

The hi-lo analogy isn’t perfect -- we can also sometimes remove entire sections of our code from consideration without gaining any information on whether the actual problem is before or after that point.

We’ll show examples of all three of these cases in the next lesson.

Eventually, with enough guesses and some good technique, we can home in on the exact line causing the problem! If we’ve made any bad assumptions, this will help us discover where. When you’ve excluded everything else, the only thing left must be causing the problem. Then it’s just a matter of understanding why.

What guessing strategy you want to use is up to you -- the best one depends on what type of bug it is, so you’ll likely want to try many different approaches to narrow down the issue. As you gain experience in debugging issues, your intuition will help guide you.

So how do we “make guesses”? There are many ways to do so. We’re going to start with some simple approaches in the next chapter, and then we’ll build on these and explore others in future chapters.

3.4 -- Basic debugging tactics
3.2 -- The debugging process

37 comments to 3.3 — A strategy for debugging

  • O.N.J

    Hey great job on this tutorial am loving it.
    As you gain experience in debugging issues, your intuition will help guide you.
    Luke remember use the force with experience it will guide you. Alex great job.

  • Danger

    i love you alex you really are doing me a good fucking solid here you dont know how much i appreciate this you selfless bastard <3 <3 <3

  • Anthony

    Is there somewhere I can practice? I want to get started with making my own code.

  • omen

    Is there some program which can show us the flow of our code...

  • keshav

    and also i had used 3 sets of brackets and i had to make 1 bracket without its other brother .if i didn't do that single bracket it showed error in mine.i use visual studio 2019

  • keshav

    Please Run this code and tell how can i can i make this better because i runned it and it sucks.
    i took the help of online sources(e.g. geeksforgeeks,stack overflow)for random no. generator and to know in which lib it existed.

    • nascardriver

      There's a lesson about random number generation

    • ryan fritz

      [#include <iostream>
      #include <cstdlib>
      #include <ctime>
      using namespace std;
      int main()
      int x =(1+rand()) % 11;
      cout << x << '\n'; //this one's just for checking
      int y;

              cout << "you: ";
              cin >> y;
              cout << '\n';

                  if (y < x)
                          cout << "Bot: Too Low'\n'";
                  else if (y == x)
                      {cout << "Bot: Correct\n";}
                  else if (y > x)
                      {cout << "Bot: Too High'\n'";}
                      {cout << "Enter A Integer'\n'";}


      source for learning Random Numbers:

      //Happy Coding[i'm a amateur programmer too]

      • Keshav

        Hello Ryan fritz!
        Thank you for the suggestions that you gave me, but a suggestion coming from me is to remove

          because then the user who is playing this game would know the answer from the start.

    • Sahil

      Theres a couple of things you could do to make it a bit better.

      1. Remove the curly braces that contain the if-statements since those are not necessary and do not improve the readability.
      Also you don't need to use the single quotes for the \n when you put already put it inside the double quotes -- "Hello World!\n"

      2. Create a bool variable and set it to true, and use it as your while loop condition and when the guess is correct, set the bool to false. In your current program the while loop will continue even after the correct guess. The bool method will terminate the loop if the user enters the correct number. Or if you wanted to limit the number of guesses you could just use an int variable to keep track of the guesses and use an expression to limit the number of iterations your loop goes through

  • sami

    "That at least rules out the parts of the program that execute after the first observable symptom."

    Hi, does the sentence above say, "the part after the point where the program exhibits the first incorrect symptom, will execute and work correctly and we don't have to cover it
    I mean, "We just have to cover parts between beginning of the program and the part where the first incorrect symptom exhibits, and we do not have to cover the rest parts, parts after symptoms occur?

    • nascardriver

      It means that we don't have to cover the rest, because we already know that the problem is before that.
      Imagine you own a 1000km long one-way road and people complain about getting flat tires (But they keep on driving). To figure out what's wrong with your road, you go to km 500 and see that people have flat tires there already. Obviously, the problem is closer to the beginning than it is to the end. You don't have to walk down the road, because that's not where the problem is. You can ignore km 500 and down, and continue searching up the road.

  • hi

    Typo "And it actually output nothing at all" should be "outputs nothing at all"

  • Apaulture

    We can use a similar process to debug programs. .. That at least rules out* the parts of the program that execute after the first observable symptom.

    I think this is what you meant to say?

  • Kermit

    i dont if it was intended but i noticed this " with the goal of honing in on the problem quickly " honing is homing*?

  • Phlier

    Alex, I know you're a busy guy, and have much better things to be doing that correcting stupid things. Keeping that in mind, I've been struggling with whether or not I should even bring this to your attention. However, you seem like a guy that put a *ton* of work into this course, and it looks very professional; courses that look professional didn't get there by accident. It shows that the author really cares. So I'd like to offer a very small correction.

    You "hone" a knife, or a skill. But you don't "hone in" on a skill. ;) You "home in" on a problem or an issue. Think of it like a missile homing in on its target.

    I know, this is overly anal and pedantic, but your site looks so amazing that I thought I'd point it out. Here's a site that'll really home in on the answer:

    There's an instance of "hone in" on the previous page that should be fixed, too.

    And here I thought syntax errors were easy to fix?? : )

  • dib

    I have started to add pieces of code to try and isolate any problems, something as simple as
    std::cout << "pass 1";
    std::cout << "pass 2";
    std::cout << "pass 3";
    tends to work, it will print the message up until the error.

  • p1n

    Hey Alex,i wondered how high can one go with these tutorials?Are they based to teach you to professionally work with C++ or something lower?

    • Hi!

      These tutorials teach the core language. In order to work professionally, you'll need to learn about language-independent concepts (eg. data structures and algorithms) and libraries. And you'll need lots of practice.
      Have a peek at the end of these lessons for more information

      • p1n

        Alright, thanks.These tutorials are awesome, so after them is something like Udemy or Codecademy good to learn more about those concepts?

        • I don't know either of those, maybe someone else can answer your question.

          • p1n

            Well, they have courses on Data Science(Python,SQL,Data Visualization,Machine learning),Computer science(git) , Web Development , aswell as courses on languages like javascript,html and python.
            I just wonder after i'm done with this site what else is left to learn?
            Are any of those things i mentioned important?

            • Omran

              if you want to be a professional software developer or learn a lot about data science or networking or even game development with c++ i prefer this website
              , i know codeacademy and udemy and to be honest they're just
              Superficial learning  , that's what i think tho , have fun!! , by the way the learncpp website is awesome i'm learning a lot of stuff from it

  • John

    Hi, I just want to thank you for your work, I've completed the whole learncpp course and when I came back to check some old topics I forgot, I noticed that learncpp came out with new topics. I'm of course looking forward donating this website on a monthly basis!
    Thank you again!

  • Red Lightning

    "The we can use a hi-lo like strategy to try and isolate where the problem actually is."
    The The is a syntax error, it needs to be Then.

  • Alex

    When debugging a program, in most cases the vast majority of your time will be spent trying finding

    *trying to find

  • mike

    In the sentence "then we know the program must have occurred before that point" it should be "problem" not "program"

Leave a Comment

Put all code inside code tags: [code]your code here[/code]