In the course of working on any programming project you will invariably run into a spot where your code just plain doesnât work the way you want it to. It might not compile and give you some super cryptic error message. It might run but give the wrong answer, or run but never complete. Or it might run and crash for mysterious reasons. When this happens, youâll need to debug your error before you can move on.
As you may know, we have LaIR hours every Sunday through Thursday. If you ever get really, really stuck, we recommend stopping on by to get some help.
Knowing how to debug effectively is one of the single most important skills you can have as a programmer. When youâre working outside the classroom â whether itâs in a research lab, an independent project, a company, a nonprofit, etc. â you wonât have the luxury of the LaIR to help you get unstuck, and yet youâll still be expected to deliver results. Accordingly, if you ask for help in the LaIR, the section leaders are likely to inquire into more depth about how youâve tried to fix the error on your own. If you havenât made much of an effort to do so, the SLs are more likely to help show you how to debug your code than they are to find and fix the bug for you.
Before you go to the LaIR to get help, we recommend that you work through this handout. In doing so, thereâs a good chance that youâll figure out what the underlying error is and how to fix it. In the event that you arenât able to sort out the issue, then youâll have a much more targeted and focused question to bring to the LaIR.
Without further ado, hereâs our advice about how to debug your code.
Step Zero: Don't Panic
Thereâs an old joke that goes like this:
- Q: Whatâs the first thing you should do when you find yourself stuck in hole?
- A: Stop digging!
When youâre writing a program and you find that it doesnât work, your instinct might be to try to take the code you have and change it to fix it. In some cases, this is totally fine. Maybe you look at the code and think âoh, whoops, I forgot a semicolonâ or âoh whoops, I forgot a return statement.â Thatâs all fine and well.
But if youâre in a spot where the code doesnât work and you really legitimately donât know whatâs going on, one of the worst things you can do is to start making changes to your code without a plan. Now you have two bugs â the bug you originally had, plus the new one you just inserted by changing things.
An important thing to keep in mind when debugging is that the bug canât move. Itâs sitting there in your code, hiding in plain sight, waiting to do the wrong thing. If you donât make changes to your code, then it canât move anywhere. It canât sneak from one part of the code into the other. Itâs just waiting to be found.
âWhen debugging, novices insert corrective code; experts remove defective code.â
So donât go changing anything. Instead, switch into debugging mode. Put on your sleuthing hat, and get ready to go on a bug hunt.
With that having been said, your first step is to figure out what the real underlying problem is.
Step One: Identify the Specific Issue You Have
The first question you should ask yourself is
What is the specific problem that youâre running into?
It might seem silly to ask this, but itâs important to make sure that you understand what specifically the issue is that youâre running into. If you show up in the LaIR and all you can say is âmy program doesnât work,â itâs unlikely that anyone is going to be able to help you fix the issue, since that information by itself isnât precise enough for the section leaders to know whatâs going on.
As a first step, try to classify the error youâre getting into one of four different categories:
-
Compiler error: The program doesnât even compile.
-
Program crash: The program compiles and runs, but it crashes.
-
Infinite loop: The program just seems to hang without anything happening.
-
Logic error: The program runs, but it produces the wrong answer.
Once youâve identified what class of error youâre getting, you can take some next steps to pin that error down more precisely.
Once youâve identified what class of error youâre getting, you can take some next steps to pin that error down more precisely.
-
Compiler error: What is the specific error message youâre getting? Is it localized to a particular line of code? If so, what line of code is that? If you have multiple different compiler errors in the same program, always just look at the first of them, since follow-up errors often result from the compiler trying to recover from an earlier error and failing.
-
Program crash: On what specific line is the program crashing? What sort of error are you getting back? Can you get a stack trace? Make sure you can answer all of these questions, since theyâll be important for smoking out the bug later on. In most cases, you can figure out where the crash occurs by running the program in debug mode and waiting for the debugger to pop up when something goes wrong. Once youâve found the crash, youâll need to localize it; see the âLocalizing a Bugâ section later on.
-
Infinite loop: Where in the program is the infinite loop? Donât guess â use the debugger to find out. Run the program in debug mode, and when it seems to be hanging, click the âinterruptâ button (it looks like a giant pause button) to pause the program at its current point. That should give you a stack trace of where you are in the program, which will pinpoint the specific area where the infinite loop occurs. You might find, sometimes, that your program isnât in a loop but is just paused and waiting for you to type something in, in which case, problem solved!
-
Logic error: If youâre getting the wrong answer, what specific function is producing the wrong answer? Once youâve found that function, there are a couple reasons why it could be giving a wrong answer. It might, for example, give back the wrong answer because the input that was provided to it was incorrect and the error is actually earlier on in the program. Therefore, your task at this point is to localize where the bug is (see the âLocalizing the Bugâ section).
A unifying theme in the above discussion is that, when possible, youâll want to use your tools to help you figure out whatâs going wrong. When you first have a program that isnât working, it can seem really overwhelming, but by asking the right questions you can convert the Hairy Scary problem of âitâs brokenâ into a more concrete question, like âwhat exactly is it about this particular loop that makes it run forever?â or âwhy does this particular line of code cause a crash?â
A unifying theme in the above discussion is that, when possible, youâll want to use your tools to help you figure out whatâs going wrong. When you first have a program that isnât working, it can seem really overwhelming, but by asking the right questions you can convert the Hairy Scary problem of âitâs brokenâ into a more concrete question, like âwhat exactly is it about this particular loop that makes it run forever?â or âwhy does this particular line of code cause a crash?â
Localizing a Bug
In some cases, youâll end up finding that your program does the wrong thing at a particular step â maybe it reads off the end of a string (oops), or perhaps it computes and returns the wrong value (oops). When that happens, youâll have to identify the root cause of the problem before you can move on so that you know where to focus your efforts.
As an example, letâs suppose that you want to write a function that takes in a string and counts up how many distinct characters it has. For example, âhiâ has two distinct characters, while âhelloâ has four.
One way you can do this is to sort the characters in the string alphabetically, then count up how many different ârunsâ of equal characters there are. For example, given the string âBanana,â youâd form the string âaaaBnn,â which has three runs of equal characters (aaa, B, nn) and therefore has three different characters. You could imagine writing the function like this:
int distinctCharactersIn(string input) {
string sorted = sort(input);
return countRunsIn(input);
}
Now, imagine youâre running this function and you find that it gives back the wrong answer on the input âBanana,â and this shows up because the starter code compares your answer to a reference answer and says that it has the wrong answer. You can be fairly confident that the issue is that the distinctCharactersIn
function is returning the wrong value, but until you know why itâs returning the wrong value itâs going to be hard to do anything about it. For example, here are four different things that could go wrong here:
-
The input provided to the function is somehow wrong, so youâre counting the number of distinct characters in the wrong string. (You sometimes hear this referred to as âgarbage in, garbage out:â if you ask the wrong question, you get the wrong answer.)
-
The
sort
function doesnât correctly sort the characters of the string. -
The
countRunsIn
function doesnât correctly count how many runs of characters there are. -
The
countRunsIn
function is being called with the wrong arguments. -
Fundamentally, the logic being used is incorrect.
Without digging into your code a bit more, itâs hard to know for sure which of these errors is the real one. Therefore, when you find this bug, the first step is to localize it to a particular region of the code. Here are some things you may want to do:
-
Run the program with the debugger turned on. Set a breakpoint at the top of the function.
-
Confirm that the input to the function is what you think it should be. If itâs supposed to be âBanana,â make sure that that is indeed what youâre working with. If not, youâve found your error. The question then is âwhy is it that my function isnât getting the input I thought it was going to get?,â and you can work backwards to see how that happened.
-
Step over the
sort
function. What output do you get fromsort(input)
? What output did you expect to get fromsort(input)
? If these disagree, youâve found whatâs going on â the real culprit is thatsort(input)
is broken. Now you can start looking atsort
to see whatâs wrong. -
Step over the
countRunsIn
function. What output do you get from it? What output did you expect to get from it? If they disagree, youâve found the error â youâre not properly counting runs, and that means you should look atcountRunsIn
. -
Step into the
countRunsIn
function. What is the input to the function? How doescountRunsIn
operate, and what calculation will it make to that input? -
Look at the overall return value. If you correctly sorted the input and correctly counted runs and yet you still arenât getting the right answer, that means that the error is possibly a logic error. Maybe sorting the characters and counting runs just isnât a good way to solve this problem.
The process described here â check that the inputs are right, go step by step and make sure that each step does the right thing, then check that the output matches what you expected â is an incredibly valuable way to figure out whatâs going on in your program. In the course of performing these steps, you might end up discovering the exact error and wonât need to proceed any further. If you can show up in the LaIR and tell the SLs that youâve worked through this process and done your best to localize the error, youâre in great shape.
Step Two: Determine How to Trigger the Problem
Okay, youâve now localized the error. If itâs a compiler error, you know what line itâs on. If itâs a crash, you know which line crashes and youâve figured out whether the crash is because the line got the wrong input or because it has the right input and still crashes anyway. If itâs an infinite loop, you know where the loop is. If itâs a logic error, youâve figured out where specifically things arenât going right. In other words, ideally, at this point, you can confidently answer this question:
Where in the program is the root cause of the issue youâre seeing?
The next question is to figure out how you can trigger the issue. Letâs go back to our example from before. Suppose the code gives the wrong answer when given the input âBanana.â Hereâs a question: will the code always give back the wrong answer, regardless of the input, or is there something special about âBanana?â If thereâs something special about âBanana,â does it have to do with the fact that it has some letters that repeat (a and n), some letters that donât (B), or a mix between upper-case and lower-case letters? Or does it have to do with which letters are repeated? Or does it have to do with how many total characters there are? Your next step is to answer this question:
In what cases does this error occur?
In coming up with an answer to this question, youâll narrow down the scope of the cause of the problem so that you can get a sense of what to look for.
Letâs go back to our âBananaâ mishap from before. A great next step in that case would be to try running the program on inputs other than âBananaâ so that you can try to sort out whatâs going wrong. It wouldnât be a bad idea to try running the program on a bunch (no pun intended) of other strings so that you can peel back (pun intended) the veil of mystery about whatâs going on.
If you have no idea what the issue might be, try running some different inputs through the program. Try small inputs: 0, the empty string, etc. Try large inputs. If youâre working with strings, try lower-case strings, UPPER-CASE STRINGS, and Mixed-CaSE strings. See what you find. Does the program work correctly on some cases? Write those cases down. Does the program fail on some other cases? Write those down too.
If youâre working on a program and the only way you know how to trigger the error is on a very large input (say, a large sample file), then itâs doubly important to find another case where the error occurs. Chances are the issue has less to do with the fact that the input is big and more to do with the fact that the input contains some pattern somewhere that you donât handle properly. One technique for figuring out the issue is to take the test file youâre getting the wrong answer on and to dramatically shrink it. Cut out the first half, or last half, and see if you still get an error. If so, cut that half in half, etc. Eventually, youâll zero in on the issue.
If that doesnât work, look at the smaller test cases that you are passing. Is there anything that they all have in common? If so, try creating your own test cases that look different from those ones. Working with text files, and all the inputs are free of spelling errors? Mash the keyboard and generate some garbage. Working with numbers? Try odd numbers, even numbers, negative numbers, 0, fractions, etc. Just throw things at the program until you can reproduce it.
If you canât get the error to show up in other cases, or if you can only get the error to show up in super large files, that in itself might be worth a trip to the LaIR so that you can get the section leaders, who all have their share of Debugging Battle Scars, to offer some input.
If you do have a better sense of what sorts of inputs cause the error and which sorts donât, look over them and see if thereâs a pattern. In many cases there will be something obvious â you get the answer wrong for any strings of length two or more, or the function crashes on anything of odd length, or it fails on anything ending in the letter e, etc. â and thatâs really, really good to know because it will inform what you should look for in the next step. Alternatively, if the program always gives the wrong answer, then thatâs good to know as well â it means that thereâs something fundamentally amiss and that youâre not looking for edge cases.
Step Three: Rubber-Duck Debug
At this point, you should be able to answer this question:
Where in the program is the root cause of the issue youâre seeing, and what type of input seems to cause the problem?
Your next step is to walk through your code, one step at a time, to see why things arenât working.
Our recommendation for this step is to use a time-honored software engineering technique amusingly called rubber-duck debugging. The idea is the following. Pull out a tiny yellow rubber duck, or any other inanimate object, and then go one line at a time through the buggy region of the code and explain why every single thing youâve written is clearly and obviously correct, being as specific and precise as possible. Calling a function? Explain to the duck why you call that function and why every input to the function is clearly and obviously the right input to that function. Running a loop? Explain to the duck why of course the bounds of the loop are totally correct and that you donât have an off-by-one error in them. Proceeding recursively? Tell the duck about how itâs really obvious why you chose the right base case, and why that base case is correct, and why you return the right thing in that base case.
From experience, Iâd say that nine times out of ten youâll run into a line of code where, in explaining it to the duck, you realize somethingâs not quite right. And congrats! Youâve probably found your bug.
When youâre just getting started programming, you may run into another case â you come across a line of code that for the life of you you canât explain to the duck. Youâre not sure why itâs there or what you were thinking as you were writing it. When that happens, thatâs a sign that you may have written something that really shouldnât be there. Thatâs a great indicator that you may want to remove that line of code and then restart your process. That might instantly fix the issue, or it might expose another.
Oh, and if youâre working with a partner, your partner is a great stand-in for a rubber duck, though (hopefully) the reverse isnât true. đ
Step Four: Really Dive Deep
At this point, you can answer the following question:
Where in the program is the root cause of the issue youâre seeing, what type of input seems to cause the problem, and what does every line of code in that part of the program do?
When this happens, itâs time to go and ask for some help. This means that youâre at a point where youâre pretty sure youâre looking in the right place, and you think you know what causes it, and you think that every line of code is correct. Clearly one of those assumptions isnât right, and at this point you should go to the LaIR and get one of the section leaders to look over your code and offer some feedback.
You might find you localized the error to the wrong place in the code. You might find you misinterpreted the directions or just flat out made an error when writing test cases and thought that something working was broken or vice-versa. And you might find thereâs some weird syntactic nuance in the program where a line of code does something fundamentally different than what you expected. In each case, having outside review would be super useful, and thatâs precisely why you should ask for help!
Once youâve found the error, make a note of what you did wrong. Was it a simple typo you didnât know to look for? Was it a logic error that hadnât occurred to you? Or was it just a boneheaded mistake, the sort of thing that everyone makes every now and then? Regardless, see what you can learn from it. Maybe the SL showed you a new way to debug things, or maybe they showed you a different way of thinking about things. Maybe you now know how to read an error message you previously didnât. In any case, hopefully you walk away having learned something new.
Getting good at programming takes time, and I honestly think that a large part of it is just having had enough time to make a lot of mistakes. Over time, youâll build up an immune system for bugs â things will look fishy and youâll catch the error before you make it.
Until then, best of luck, and happy debugging!