Lesson 9Phase 1: First steps
Reading errors and debugging
Compile errors, runtime exceptions and logic bugs, how to read a stack trace, and a calm method for finding any bug.
By BasithUpdated 45 min
- compile error
- runtime error
- logic error
- stack trace
- print debugging
- breakpoints
Every programmer writes code that does not work. Beginners do, and so do people with twenty years of Java behind them. What experienced programmers have is a routine for finding the problem, and the calm to follow it. This lesson gives you that routine: how to read what Java tells you when something goes wrong, and what to do when it tells you nothing at all.
- Tell a compile error, a runtime error and a logic error apart, and say when each one shows up.
- Read a
javacmessage: the file, the line, the caret, and why you fix the first error first. - Read a stack trace from the top and find the line of your own code that crashed.
- Track down a logic error by predicting values and checking them with labelled prints or a step-through.
- Use a debugger's breakpoints, step over, step into, step out and watches.
- Work through a calm checklist whenever a program misbehaves.
7 / 2 is 3, and if and else warned about writing = where you mean ==. This lesson turns those into one method for finding any bug, using only Phase 1 tools: variables, operators, decisions, loops, arrays and ArrayList.Three kinds of problem#
A mistake in a program is called a bug, and finding and fixing it is called debugging. Bugs come in three kinds. Picture a cook working from a recipe card, and three ways the card can let them down.
- The card is so garbled that the cook will not even start: a sentence stops halfway, or a step uses an ingredient the card never listed. Nothing gets cooked.
- The cook starts, follows the steps, and reaches one that cannot be done: “crack the fourth egg”, when the card only asked for three. Cooking stops right there, halfway through.
- The cook follows every step perfectly and serves the dish, but the card said a cup of salt where it meant a teaspoon. No step failed, and the dish still comes out wrong.
Java programs fail in the same three ways.
| Kind | When you find out | What you see | Example |
|---|---|---|---|
| Compile error | Before the program runs, while javac reads it | Messages from javac, and no program to run | A missing semicolon |
| Runtime error | While the program runs | The program stops and prints a report | Asking for item 3 of an array that holds 3 items |
| Logic error | When you notice the result is wrong | Nothing unusual: the program ends normally | A total that skips the first item |
Key ideaCompile errors stop a program before it starts. Runtime errors stop it partway through. Logic errors never stop it, so you have to catch them yourself, by knowing what the right answer should be.
Java does a lot of the work for you with the first two kinds: it says what went wrong and on which line. With a logic error, Java has no idea anything is wrong, because the program did exactly what you wrote. Most of debugging is finding the gap between what you wrote and what you meant.
Average: 59.0, but the right answer is 77.4. What kind of problem is this?Compile errors: Java refuses to start#
In your first Java program you saw that javac, the compiler, turns your .javafile into bytecode before anything runs. On the way it checks that every line follows Java's grammar and that every value fits its type. If something does not, it prints what it found, makes no .class file, and stops. That is a compile error.
Here is a program with one character missing. Try to spot it before you read what javac says.
public class Main { public static void main(String[] args) { int apples = 5; int pears = 3 int fruit = apples + pears; System.out.println("Fruit: " + fruit); }}$ javac Main.javaMain.java:4: error: ';' expected int pears = 3 ^1 errorReading the message
Every message from javac has the same parts, so once you can read one you can read them all.
Main.javais the file. In a project with many files, it tells you which one to open.4is the line number. Editors show line numbers down the left edge of the code.error:means the file did not compile. Messages markedwarning:point at something suspicious but still let the program compile.';' expectedis the message itself: whatjavacwanted to see and did not.- Below that,
javaccopies the line and puts a caret, the^symbol, under the spot where it got stuck. 1 errorat the bottom counts the errors in the whole file.
The caret sits just after the 3, where the semicolon belongs. javac read int pears = 3, then met int on the next line while it was still waiting for the statement to end.
javac reports where it noticed the problem, which is usually the line with the mistake. When it is not, the mistake is almost always a little earlier: a missing semicolon, quote or bracket only becomes a problem when Java reaches the code that follows it.cannot find symbol
This is the error you will see most. A symbolis any name in your code: a variable, a method, a class. “Cannot find symbol” means you used a name Java does not know, at least not in that spot.
public class Main { public static void main(String[] args) { int score = 42; System.out.println("Score: " + scor); }}$ javac Main.javaMain.java:4: error: cannot find symbol System.out.println("Score: " + scor); ^ symbol: variable scor location: class Main1 errorThe two extra lines say which name Java could not find (variable scor) and where it was looking (class Main). The usual causes are a typo like this one, different capital letters (to Java, Total and total are two different names), a variable used outside the braces it was declared in, and a variable that was never declared at all.
The same message turns up for methods and classes, and the symbol: line says which kind of name went wrong:
| You wrote | The symbol line names | The fix |
|---|---|---|
System.out.printn("Hello"); | method printn(String) | A typo in a method name. It is println. |
string name = "Ana"; | class string | Class names start with a capital letter: String. |
A loop's i, used after the loop | variable i | A variable declared in a for line exists only inside that loop. Declare it before the loop if you need it afterwards. |
incompatible types
Every variable has a type, and javac checks that each value you store fits it, as you saw in variables and types. These two versions of the error are the ones you will meet most. The line javac copies is all you need to see:
$ javac Main.javaMain.java:3: error: incompatible types: possible lossy conversion from double to int int price = 4.99; ^1 error4.99 is a double. Putting it in an int would throw away the .99, which is what “lossy” means, and Java will not do that unless you tell it to. Usually the fix is to make the variable a double. If you really want only the whole part, say so with a cast, (int) 4.99, from operators and casting.
$ javac Main.javaMain.java:3: error: incompatible types: String cannot be converted to int int age = "12"; ^1 errorThe quotes make "12" a String, a piece of text, even though it looks like a number, and text never turns into an int by itself. Drop the quotes. When the text really comes from outside the program, convert it with Integer.parseInt, which appears later in this lesson.
missing return statement
This one comes from a tool you have not built yet. A method is a named block of code that other code can call; main is one, and some methods hand a value back with return. You will write your own in methods, and this message is waiting for you there, so here is a preview. grade is meant to hand back "pass" or "fail" for a score:
public class Main { public static void main(String[] args) { System.out.println(grade(72)); } static String grade(int score) { if (score >= 50) { return "pass"; } else if (score < 50) { return "fail"; } }}$ javac Main.javaMain.java:12: error: missing return statement } ^1 errorThe line static String grade(int score) promises a String back every time. You can see that a score is either at least 50 or below 50, so one of the two return lines always runs. javac does not work out values like that. It sees an if and an else if, allows for both conditions being false, and finds a way to reach the closing brace on line 12 without returning anything. The caret points at that brace. A plain else fixes it, because javac knows an else runs whenever the if does not.
reached end of file while parsing
public class Main { public static void main(String[] args) { int[] scores = {72, 85, 90}; for (int i = 0; i < scores.length; i++) { System.out.println(scores[i]); }}$ javac Main.javaMain.java:8: error: reached end of file while parsing} ^1 errorParsing is javacreading your code to work out its structure: which statements sit inside which braces. “Reached end of file while parsing” means the file ended while something was still open. Every { needs a matching }, and the for loop's closing brace used to be on line 6. The indentation suggests that line 7 closes main, but Java ignores indentation and pairs each } with the most recent { that is still open. So line 7 closes the loop, line 8 closes main, and the class is still open when the file runs out.
Fix the first error first
One mistake can produce several messages. Here a variable was misspelled where it was declared:
public class Main { public static void main(String[] args) { int[] prices = {4, 10, 6}; int totl = 0; for (int i = 0; i < prices.length; i++) { total = total + prices[i]; } System.out.println("Total: " + total); }}$ javac Main.javaMain.java:6: error: cannot find symbol total = total + prices[i]; ^ symbol: variable total location: class MainMain.java:6: error: cannot find symbol total = total + prices[i]; ^ symbol: variable total location: class MainMain.java:8: error: cannot find symbol System.out.println("Total: " + total); ^ symbol: variable total location: class Main3 errorsOne mistake caused all three errors. Line 4 declares totl, so every later total is a name Java has never heard of. None of the messages points at line 4, where the real mistake is, but the first one still leads you there: Java cannot find total, and asking “where did I declare total?” takes you straight to line 4. Correct it and all three messages disappear.
Sometimes the extra messages are stranger. This program is missing a single { at the end of line 4:
public class Main { public static void main(String[] args) { int[] scores = {72, 85, 90}; for (int i = 0; i < scores.length; i++) System.out.println(scores[i]); } System.out.println("Done"); }}$ javac Main.javaMain.java:7: error: <identifier> expected System.out.println("Done"); ^Main.java:7: error: illegal start of type System.out.println("Done"); ^Main.java:9: error: class, interface, annotation type, enum, record, method or field expected}^3 errorsNone of the three messages mentions line 4. Without its {, the loop body is only line 5, so the } on line 6 closes main early. Java then reads line 7 as if it sat directly inside the class, where a statement such as println is not allowed. The }on line 8 closes the class, and the one on line 9 has nothing left to close. Each message is true from Java's point of view, and each one comes from the single missing brace.
Key ideaFix the first error, then compile again. Later messages are often side effects of the first one, and one fix can clear a whole list.
The opposite happens too: fixing an error can reveal a new one. javac works in stages, and when the grammar is broken it stops before it checks types. This program has a missing semicolon on line 4 and a type mistake on line 6:
public class Main { public static void main(String[] args) { int apples = 5; int pears = 3 int fruit = apples + pears; String label = fruit; System.out.println(label); }}The first compile reports only the semicolon:
$ javac Main.javaMain.java:4: error: ';' expected int pears = 3 ^1 errorAdd the semicolon, compile again, and the second mistake appears:
$ javac Main.javaMain.java:6: error: incompatible types: int cannot be converted to String String label = fruit; ^1 errorA new error after a fix means javac got further through your file. That counts as progress, even when the number of errors stays the same.
int price = 4.99; and quietly keep the 4, a program javac refuses. Every compile error on this page comes from the real javac, so these are the messages you will see on your own machine.javac reports four errors, on lines 6, 9, 12 and 15. What should you do first?Runtime errors: the program crashes#
A program can compile cleanly and still hit a wall while it runs. Some things Java can only check at the moment they happen: whether an index is inside the array, whether the number you divide by is zero, whether a variable really refers to something. When one of those checks fails, Java throws an exception: it packs up a description of the problem, called an exception, and abandons whatever it was doing. If nothing catches the exception, the program ends. (Catching them is a later topic.)
Press Run. When the run stops, use the back arrow to step backwards from the crash and look at i just before it happened.
public class Main { public static void main(String[] args) { String[] players = {"Ana", "Ben", "Chen"}; for (int i = 0; i <= players.length; i++) { System.out.println("Player " + i + ": " + players[i]); } System.out.println("Everyone is ready"); }}ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
Array positions run from 0 up to length - 1. This index fell outside that range.
The three players print, then the program stops on line 5, and Everyone is ready never appears: once an exception goes uncaught, nothing after it runs. The lines marked ! in the output are the report Java prints. They go to standard error, a second output channel meant for problems, which editors and terminals can show apart from normal output.
Reading a stack trace, top down
The report is called a stack trace. Here it is as plain text:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3 at Main.main(Main.java:5)- 1
Exception in thread "main"says the program's main line of work crashed. A threadis Java's word for one line of work, and every program you have written so far has just one, calledmain. - 2
java.lang.ArrayIndexOutOfBoundsExceptionnames the kind of problem. Thejava.lang.part is the package it belongs to, a named group of Java's own classes (more on those in packages and imports). The name on its own often tells you what happened. - 3After the colon comes the message,
Index 3 out of bounds for length 3: the code asked for index 3 of an array whose length is 3, so its last index is 2. - 4Each line that starts with
atis a place in the code.at Main.main(Main.java:5)reads as classMain, methodmain, fileMain.java, line 5. That is the line to open.
When the bad index equals the length, as it does here, the loop almost certainly used <= where it needed <.
Dividing a whole number by zero
public class Main { public static void main(String[] args) { int slices = 12; int friends = 0; System.out.println("Sharing " + slices + " slices"); int each = slices / friends; System.out.println("Each friend gets " + each); }}ArithmeticException: / by zero
Whole numbers cannot be divided by zero. Check the divisor before dividing.
This time the report reads ArithmeticException: / by zero. Dividing a whole number by zero has no answer, so Java stops on line 6. The fix belongs before the division: make sure the divisor cannot be zero, for example with an if (friends > 0) around it.
Using null as if it were something
A String variable can hold null, the special value that means “nothing here”, which you met in variables and types. A new String[] array starts with null in every slot, the way a new int[] starts with zeros. Asking null for its length, or for anything else, is impossible, and Java throws a NullPointerException.
public class Main { public static void main(String[] args) { String[] names = new String[3]; names[0] = "Ana"; names[1] = "Ben"; String last = names[2]; System.out.println("The last name is " + last); System.out.println("It has " + last.length() + " letters"); }}NullPointerException: Cannot invoke "String.length()" because "last" is null
A variable held null, so there was no object to use.
Notice two things. Printing null works: joined to a String, it turns into the text null, which is a clue in itself. What crashes is calling a method on it, on line 8. And the message says exactly what happened: Cannot invoke "String.length()" because "last" is null. It names the call that failed and the variable that held null. Messages this detailed arrived in Java 14 and have been switched on by default since Java 15.
Text that is not a number
Programs often receive numbers as text, typed into a box or read from a file. Integer.parseInt reads a whole number out of a String: Integer.parseInt("42") gives the int 42. When the text is not a whole number, it throws a NumberFormatException. In this list someone typed the letter o where they meant a zero:
public class Main { public static void main(String[] args) { String[] typed = {"12", "7", "4o"}; int total = 0; for (int i = 0; i < typed.length; i++) { total = total + Integer.parseInt(typed[i]); System.out.println("Running total: " + total); } }}NumberFormatException: For input string: "4o"
The text was not a number Java could read, such as "abc" or "12.5" for an int.
The message, For input string: "4o", shows the text it could not read inside quotes, so even a stray space would be visible. The two running totals printed first tell you the first two values were fine.
Which line is yours?
In the browser that trace has a single at line. Compile and run the same program in a terminal, and real Java prints more:
$ javac Main.java$ java MainRunning total: 12Running total: 19Exception in thread "main" java.lang.NumberFormatException: For input string: "4o" at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67) at java.base/java.lang.Integer.parseInt(Integer.java:527) at java.base/java.lang.Integer.parseInt(Integer.java:624) at Main.main(Main.java:6)The at lines list the methods that were running at the moment of the crash, newest first, so the method on each line was called by the method on the line below it. Read from the bottom up, they tell the story: main, on line 6, called Integer.parseInt. That method called a second version of parseInt, which called forInputString, which built the exception.
Lines that start with java.base/are inside Java's own library. That code has been used and tested for decades, so the bug is almost never there. Skip down to the first line that names your own file, here Main.java:6: the line that handed "4o" to parseInt.
Integer.java:527, change from one Java version to the next, so your terminal may show different ones. The browser leaves the library lines out and shows only the lines in your code.Key ideaRead a stack trace from the top. The first line says what went wrong. The first
atline that names your own file says where to look.
These are the runtime errors you will meet most often in Phase 1 programs:
| Exception | What went wrong | A typical cause |
|---|---|---|
ArrayIndexOutOfBoundsException | An array index that is negative, or the length or bigger | <= in a loop condition, or a size typed in by hand |
IndexOutOfBoundsException | The same for an ArrayList: a position outside 0 to size() - 1 | list.get(list.size()) |
ArithmeticException | A whole number divided by zero, with / or % | A count or divisor that can be 0 |
NullPointerException | Using null as if it were a String, array or list | A variable or array slot that was never given a value |
NumberFormatException | Text that is not a whole number given to Integer.parseInt | Letters, spaces or a decimal point, as in "12.5" |
NumberFormatException trace above, which line should you open first to fix the bug?Logic errors: it runs, but the answer is wrong#
A logic error comes with no message, no line number and no stack trace. The program runs to the end and prints something wrong, or does the wrong thing. The only alarm is you noticing that the result does not match what you expected, so you need to know the right answer first. Work it out by hand for a small example before you trust the program's answer.
Off by one, and other wrong bounds
In loops the off-by-one mistake made a loop run once too often or once too few. With arrays it often does not crash at all. This program should add up a week of step counts, and by hand 4200 + 6100 + 3900 + 8000 is 22200:
public class Main { public static void main(String[] args) { int[] steps = {4200, 6100, 3900, 8000}; int total = 0; for (int i = 1; i < steps.length; i++) { total = total + steps[i]; } System.out.println("Steps this week: " + total); }}It prints Steps this week: 18000. The loop starts at 1, so the first day, at index 0, is never added. There is no crash and no message, only a total that is 4200 short.
Bounds go wrong in nested loops too. In this grid of cinema seats a 1 means taken, so four seats are taken:
public class Main { public static void main(String[] args) { int[][] seats = { {1, 0, 1}, {1, 1, 0} }; int taken = 0; for (int r = 0; r < seats.length; r++) { for (int c = 0; c < seats.length; c++) { taken = taken + seats[r][c]; } } System.out.println("Seats taken: " + taken); }}It prints Seats taken: 3. The inner loop stops at seats.length, the number of rows, which is 2, so column 2 is never checked. As two-dimensional arrays showed, the number of columns in a row is seats[r].length.
length - 1, with the loop stopping right after it.One equals sign where you meant two
If and else warned that = stores a value and == compares two. With an int, Java catches the slip for you, because an if needs a boolean:
$ javac Main.javaMain.java:4: error: incompatible types: int cannot be converted to boolean if (lives = 0) { ^1 errorlives = 0 stores 0 in lives, and the whole expression has the value 0, an int. An int is not a yes-or-no answer, so javac refuses. With a boolean variable the same slip compiles, and the program quietly does the wrong thing:
public class Main { public static void main(String[] args) { boolean gameOver = false; if (gameOver = true) { System.out.println("Game over"); } else { System.out.println("Keep playing"); } System.out.println("gameOver is now " + gameOver); }}It prints Game over and then gameOver is now true. gameOver = true stores true and the if then tests that value, so the first branch always runs and the variable has changed as well. Write ==, or simply if (gameOver), since a boolean already is a yes-or-no answer.
Whole-number division
The average of 72, 85 and 90 is a little over 82.33. This program gets something else:
public class Main { public static void main(String[] args) { int[] scores = {72, 85, 90}; int total = 0; for (int i = 0; i < scores.length; i++) { total = total + scores[i]; } double average = total / scores.length; System.out.println("Average: " + average); }}It prints Average: 82.0, and that .0 is the clue. The variable average is a double, but Java works out the right-hand side first, and total / scores.length is an int divided by an int, which drops the fraction, as operators and casting showed. The result, 82, becomes 82.0 only afterwards, when it is too late. Cast before dividing: (double) total / scores.length gives 82.33333333333333.
Forgetting to update a variable
A variable that should change inside a loop, but never does, is easy to miss because the code around it looks finished. Savings start at 100 and grow by 20 a week until they reach 150. Predict the output, then run it.
public class Main { public static void main(String[] args) { int savings = 100; int weeks = 0; while (savings < 150) { savings = savings + 20; } System.out.println("Reached " + savings + " after " + weeks + " weeks"); }}What does this print? Work through the loop on paper, writing down savings and weeks after each pass.
Ways to find a bug#
Compile errors and crashes point at a line. A logic error does not, so you need ways to close in on it. Programmers use the methods below every day, and you can use all of them already.
Explain it to a rubber duck
Read your code aloud, one line at a time, and explain what each line does, to a friend, a pet, or a rubber duck on your desk. It sounds silly, and it works more often than you would think. To explain a line you have to say what it really does, and that is when you hear yourself say “and then i starts at 1, which skips... oh.” This is called rubber duck debugging.
Predict, then check
Before you run a program, write down what you expect it to print, or what a variable should hold at some point. Then run it and compare. The first place where Java disagrees with you is either the bug or a gap in your understanding, and both are worth finding. Every Predict box in these notes is practice for this habit.
Labelled print statements
The quickest way to see inside a running program is to print what it holds. Add a println inside the loop, run the program, and read the values. Give every debug print a label, so you know which print produced each line and which variable each number belongs to. Here is the step counter again, with one labelled print at the top of the loop:
public class Main { public static void main(String[] args) { int[] steps = {4200, 6100, 3900, 8000}; int total = 0; for (int i = 1; i < steps.length; i++) { System.out.println("DEBUG i=" + i + ", steps[i]=" + steps[i] + ", total=" + total); total = total + steps[i]; } System.out.println("Steps this week: " + total); }}The first debug line already says i=1. The loop never visited index 0, so the 4200 was never added, and one run has found the bug.
DEBUG, so the lines are easy to spot and easy to remove. Print each name next to its value: total=6100 tells you far more than a bare 6100. When the bug is fixed, delete the debug prints.Tap a line, then tap where it should go. Or use the arrows.
The visualizer is a debugger
Every Run button in these notes opens a small step-by-step debugger. Press Run, pause, and use the arrows to move one step at a time. The line about to run is lit in the code, and the trace panel shows each variable in the call stack and each array in memory as it is at that step. Two features help with bugs in particular:
- After a crash, the run ends on the error step. Step backwards from there to see the values that led to it. Most debuggers in editors can only move forwards.
- The slider jumps to any moment, so you can find the first step where a value goes wrong and look at the line that did it.
Try it on the first step counter in this lesson: step through it and find the moment total first differs from your own running total.
Cut the search in half
When a long program goes wrong and you have no idea where, stop reading it line by line and halve the search instead. Pick a point near the middle and check whether the values are still right there, with a labelled print or a breakpoint. If they are already wrong, the bug is in the first half; if they are right, it is in the second. Then halve that half. A 64-line program needs only about six checks, because 64 halves to 32, 16, 8, 4, 2 and finally 1.
The same trick works on a compile error you cannot make sense of. Turn half the code into comments with //, which javac skips, and compile again. If the error disappears, it was in the half you hid. Bring lines back a few at a time until it returns. Comment out whole statements and whole blocks, or you will create new errors of your own.
The debugger in your editor#
Editors such as IntelliJ IDEA and VS Code come with a debugger: a tool that runs your program but lets you pause it, look at every variable, and go on one line at a time. It is the visualizer's idea, applied to your own programs on your own machine. The names of the controls are nearly the same in both editors:
| Control | What it does |
|---|---|
| Breakpoint | A marker you set by clicking beside a line number. The program pauses just before that line runs. |
| Debug | Starts the program under the debugger. Start it with Run instead and the breakpoints are ignored. |
| Resume | Runs on until the next breakpoint, or to the end. VS Code calls it Continue. |
| Step over | Runs the current line completely, including any method it calls, and pauses on the next line that runs. |
| Step into | When the current line calls one of your own methods, goes inside and pauses on its first line. |
| Step out | Finishes the current method and pauses back in the method that called it. |
| Variables | Lists the variables of the method you are paused in, with their current values. |
| Watches | Expressions you add yourself, such as total or prices[i], worked out again at every pause. |
Try them on this pretend debugger. A breakpoint is already set on line 6; press Debug, then use the buttons. withTax is a method of our own: main calls it with a price, and it hands back the price plus 10 percent tax. You will write methods like it in methods; here it is something to step into.
public class Main { public static void main(String[] args) { int[] prices = {20, 5, 12}; int total = 0; for (int i = 0; i < prices.length; i++) { total = total + withTax(prices[i]); } System.out.println("Total: " + total); } static int withTax(int price) { int tax = price / 10; return price + tax; }}Not running yet. The dot by line 6 is a breakpoint: click any line number to set or clear one, then press Debug.
Frames
Variables
Watches
Console
Some things to try:
- On line 6, press Step over and the whole call runs in one go. Press Step into instead and you land inside
withTax, with a second method listed under Frames. - Inside
withTax, the watches report no variabletotalorprices, because those belong tomain. Clickmainunder Frames and they come back. - From inside
withTax, Step out finishes the method and lands back on line 6. - Clear every breakpoint and press Debug. With nowhere to pause, the program runs straight to the end.
total = total + withTax(prices[i]);, and you want to watch withTax work out the tax. Which button do you press?A calm checklist#
When something goes wrong, the worst move is to start changing things at random. Each random change can add a new bug, and you learn nothing about the old one. Work down this list instead, and stop when you have found the problem.
- 1Read the whole message, slowly. For a compile error, find the file, the line and the caret. For a crash, read the exception name, the message and the first
atline in your own file. For wrong output, write down what you expected next to what you got. - 2With compile errors, fix the first one only, then compile again.
- 3Make the problem happen again on purpose, with the smallest input that shows it.
- 4Read the suspect lines aloud and say what each one does.
- 5Look inside: add labelled prints, or step through in the visualizer or a debugger, until you find the first line where a value stops matching your hand trace.
- 6Change one thing, then run again. If the change did not help, undo it before you try the next idea.
- 7Once it works, try the edges: an empty array, a single item, a zero, the last index. Then remove your debug prints.
- 8If you are still stuck after twenty minutes, take a short break, or explain the problem to someone else.
Key ideaDebugging is a search. Every check rules out places where the bug cannot be, until one line is left.
Now use the checklist on a program with three bugs in it.
Average: 77.4 and Top score: 92.Try it
Answer each one in your head or on paper first, then open the answer.
javac says cannot find symbol, and the next line says symbol: variable totl. Give two different mistakes that could cause this.int total = 247; and int count = 3;, why does double average = total / count; give 82.0, and how do you fix it?Cannot invoke "String.length()" because "last" is null. What failed, and why?Practice
Work through these on your own. Each one builds on this page.
String into an int, and delete a closing brace. Then copy the grade example, fix it with a plain else, and change that else back to else if (score < 50). Compile each version with javac and write the first line of each message in your notes next to the mistake that caused it.total goes wrong. Write down what the Variables panel showed at that moment.weeks bug so that it prints Reached 160 after 3 weeks. Then break it differently: delete the line savings = savings + 20;. Predict what will happen before you run it, run it in the playground, and explain what you see.Further reading#
Spotted a mistake, or a part that did not make sense? Report it by email