Bugs, or code errors, are unavoidable. Great programmers don't write perfect, bug-free code every time. In fact, it's usually the opposite. Great coders have created, and fixed, thousands and thousands of bugs. This provides the experience and practice to make them expert debuggers.
Debugging can be frustrating but here are three simple techniques you can use to squash those pesky bugs!
console.log() to debug malfunctioning codeLet's say you're writing a function to see if a number is smaller than ten.
function numberSmallerThanTen(number) {
if (num < 10) {
return true;
} else {
return false;
}
}
numberSmallerThanTen(5);
You run this to see if 5 is smaller than 10 and get this:
/tmp/file.js:2
if (num < 10) {
^
ReferenceError: num is not defined
at numberSmallerThanTen (/tmp/file.js:2:3)
...
This error message tells us exactly what's wrong with the code! You are trying to call num without defining it. This number should be defined as a parameter in the function. Looking there, you can see that you accidentally named your parameter number instead of num. Changing that fixes our bug.
function numberSmallerThanTen(num) { // fixed this line
if (num < 10) {
return true;
} else {
return false;
}
}
numberSmallerThanTen(5);
console.log() to debug malfunctioning codeYou learned how to use console.log() to print things out in your code. console.log is primarily used to log your code for debugging. console.log is very useful for debugging so use it liberally! Take this example:
function removeAllS(str) {
for (let i = 0; i < str.length; i++) {
// If the character at the ith position of the string is "s"
if (str[i] == "s") {
// Delete the character in the string by:
// Setting the string to the substring from the 0th position to the i - 1
// position concatenated with the substring from the i + 1 position to
// the end of the string
let firstSubStr = str.substring(0, i);
let secondSubStr = str.substring(i + 1, str.length);
str = firstSubStr + secondSubStr;
}
}
return str;
}
This function will iterate through a string and remove any oranges.
Let's try testing the code with an example input array.
Input: "discussing"
Expected Output: "dicuing"
Let's try running the code in the terminal to see the actual output. Looks good... Actually, on further inspection though, you should see a problem. Can you spot the difference between the expected output and the actual output?
> removeAllS("discussing");
'dicusing'
You're still seeing an s leftover. What's going on here? Let's use console.log()s to figure that out.
Use console.log to check if the index value in the for loop is what you expect it to be:
function removeAllS(str) {
for (let i = 0; i < str.length; i++) {
console.log("index: ", i);
// If the character at the ith position of the string is "s"
if (str[i] == "s") {
// Delete the character in the string by:
// Setting the string to the substring from the 0th position to the i - 1
// position concatenated with the substring from the i + 1 position to
// the end of the string
let firstSubStr = str.substring(0, i);
let secondSubStr = str.substring(i + 1, str.length);
str = firstSubStr + secondSubStr;
}
}
return str;
}
> removeAllS("discussing");
index: 0
index: 1
index: 2
index: 3
index: 4
index: 5
index: 6
index: 7
'dicusing'
Tracing through the loop with console.log(), you can see that you're skipping a few characters. The string of "discussing" has a length of 10, but the for loop only goes up to the 7th index, which means that it only checks 8 characters even though it should be checking all 11 characters.
Let's add another console.log to check if all the values in the for loop are what you expect it to be:
function removeAllS(str) {
for (let i = 0; i < str.length; i++) {
console.log("==== START NEXT FOR LOOP ITERATION ====");
console.log("index:", i);
console.log("element:", str[i]);
// If the character at the ith position of the string is "s"
if (str[i] == "s") {
// Delete the character in the string by:
// Setting the string to the substring from the 0th position to the i - 1
// position concatenated with the substring from the i + 1 position to
// the end of the string
console.log("str before removal:", str);
let firstSubStr = str.substring(0, i);
let secondSubStr = str.substring(i + 1, str.length);
str = firstSubStr + secondSubStr;
console.log("str after removal:", str);
}
}
return str;
}
Trace through the outputs of the console.log() to see if you can spot where the issue seems to be:
removeAllS("discussing");
==== START NEXT FOR LOOP ITERATION ====
index: 0
element: d
==== START NEXT FOR LOOP ITERATION ====
index: 1
element: i
==== START NEXT FOR LOOP ITERATION ====
index: 2
element: s
str before removal: discussing
str after removal: dicussing
==== START NEXT FOR LOOP ITERATION ====
index: 3
element: u
==== START NEXT FOR LOOP ITERATION ====
index: 4
element: s
str before removal: dicussing
str after removal: dicusing
==== START NEXT FOR LOOP ITERATION ====
index: 5
element: i
==== START NEXT FOR LOOP ITERATION ====
index: 6
element: n
==== START NEXT FOR LOOP ITERATION ====
index: 7
element: g
'dicusing'
Hint: Observe the outputs when the for loop goes from index of 4 to index of 5.
It seems like the for loop never checks the second s when there are two s's right next to each other.
It turns out, whenever you remove a character in str, everything else in str shifts over by one. So, when you remove that second s on index 4, now the second s that was on index 5 shifts to index 4. Since the for loop moves onto index 5 in the next iteration, you never end up checking that index of 4 again.
To help you understand problems or bugs, it's helpful to create visuals. Let's try to visualize what's happening in between the iterations of index 4 and 5.
At index 4 before removal:
↓
0 1 2 3 4 5 6 7 8
d i c u s s i n g
At index 4 after removal:
↓
0 1 2 3 4 5 6 7
d i c u s i n g
Next iteration - At index 5:
↓
0 1 2 3 4 5 6 7
d i c u s i n g
Another strategy is to, in comments, replace the variables in the code with the code outputs.
When the index is 4:
function removeAllS(str) { // str = 'dicussing'
for (let i = 0; i < str.length; i++) { // i = 4
if (str[i] == "s") { // str[i] = 's'
let firstSubStr = str.substring(0, i); // firstSubStr = 'dicu'
let secondSubStr = str.substring(i + 1, str.length); // secondSubStr = 'sing'
str = firstSubStr + secondSubStr; // str = dicusing
}
}
return str;
}
When the index is 5:
function removeAllS(str) { // str = 'dicusing'
for (let i = 0; i < str.length; i++) { // i = 5
if (str[i] == "s") { // str[i] = 'i'
let firstSubStr = str.substring(0, i);
let secondSubStr = str.substring(i + 1, str.length);
str = firstSubStr + secondSubStr;
}
}
return str;
}
Have you figured out how to solve this bug? There are several ways to solve this. One way you can solve this bug is by lowering the index by 1 every time you remove an s from the string:
At index 4 before removal:
↓
0 1 2 3 4 5 6 7 8
d i c u s s i n g
At index 4 after removal:
↓
0 1 2 3 4 5 6 7
d i c u s i n g
Set index to 3 after removal:
↓
0 1 2 3 4 5 6 7
d i c u s i n g
Next iteration - At index 4 again before removal:
↓
0 1 2 3 4 5 6 7
d i c u s i n g
function removeAllS(str) {
for (let i = 0; i < str.length; i++) {
console.log("==== START NEXT FOR LOOP ITERATION ====");
console.log("index:", i);
console.log("element:", str[i]);
// If the character at the ith position of the string is "s"
if (str[i] == "s") {
// Delete the character in the string by:
// Setting the string to the substring from the 0th position to the i - 1
// position concatenated with the substring from the i + 1 position to
// the end of the string
console.log("str before removal:", str);
let firstSubStr = str.substring(0, i);
let secondSubStr = str.substring(i + 1, str.length);
str = firstSubStr + secondSubStr;
console.log("str after removal:", str);
// Decrement the index after removal
i--;
}
}
return str;
}
Now you get the proper behavior:
> removeAllS("discussing");
==== START NEXT FOR LOOP ITERATION ====
index: 0
element: d
==== START NEXT FOR LOOP ITERATION ====
index: 1
element: i
==== START NEXT FOR LOOP ITERATION ====
index: 2
element: s
str before removal: discussing
str after removal: dicussing
==== START NEXT FOR LOOP ITERATION ====
index: 2
element: c
==== START NEXT FOR LOOP ITERATION ====
index: 3
element: u
==== START NEXT FOR LOOP ITERATION ====
index: 4
element: s
str before removal: dicussing
str after removal: dicusing
==== START NEXT FOR LOOP ITERATION ====
index: 4
element: s
str before removal: dicusing
str after removal: dicuing
==== START NEXT FOR LOOP ITERATION ====
index: 4
element: i
==== START NEXT FOR LOOP ITERATION ====
index: 5
element: n
==== START NEXT FOR LOOP ITERATION ====
index: 6
element: g
'dicuing'
Don't forget to remove the console.log()s when the bug is fixed!
function removeAllS(str) {
for (let i = 0; i < str.length; i++) {
// If the character at the ith position of the string is "s"
if (str[i] == "s") {
// Delete the character in the string by:
// Setting the string to the substring from the 0th position to the i - 1
// position concatenated with the substring from the i + 1 position to
// the end of the string
let firstSubStr = str.substring(0, i);
let secondSubStr = str.substring(i + 1, str.length);
str = firstSubStr + secondSubStr;
// Decrement the index after removal
i--;
}
}
return str;
}
In the previous example, you might have written the first removeAllS function, successfully tested by not testing the double s case. For example removeAllS("sparse") would have returned 'pare' without any bug fixing. You probably would have thought to yourself, I passed this test case, so I'm done with this problem and move on to the next problem.
However it's only when you try a string with two s's in a row that you find the bug.
Here is a function that counts down from a starting number then stops when it hits zero. It seems to work just fine for our test case of 5 but there is a serious bug here. Can you find it?
Hint: Try to see if you can come up with an example input that might cause the code to behave unexpectedly.
function countdown(num) {
while (num !== 0) {
console.log(num);
num--;
}
}
countdown(5);
If you try testing with a decimal (also known as a float) or a negative number, you'll find that the function keeps counting forever.
function countdown(num) {
while (num !== 0) {
console.log(num);
num--;
}
}
countdown(3.5); // will cause an infinite while loop
countdown(-10); // will cause an infinite while loop
Running the code will get you an infinite number of outputs in the terminal:
> countdown(3.5);
3.5
2.5
1.5
0.5
-0.5
-1.5
...continues on and on
Tip: If you try running this in Node.js, simply press CTRL + C to exit the infinite looping.
You can fix this by changing the while condition to run while only when the number is greater than 0.
function countdown(num) {
while (num > 0) {
console.log(num);
num--;
}
}
countdown(3.5); // no more infinite loop
countdown(-10); // no more infinite loop
Much better!
Identifying cases to test is an important part of programming. At first, you will be provided the test cases to test, but eventually, you will have to come up with your own test cases. So pay attention to the test cases that we provide you and ask yourself why do you think we gave you those test cases. This will help you start to write good test cases on your own.
No programmer writes perfect code the first time through, which is why debugging is a crucial skill to practice. If your code isn't working and you're not sure why, follow these steps and you'll be able to squash even the most elusive bugs!
console.log() to identify where the actual behavior differs.