Understanding Bugs and Errors in JavaScript

The first recorded computer bug (Credit: National Museum of American History)

In 1947, Grace Hopper made the above record in the Harvard Mark II Computer’s log book. She had encountered ‘the first actual bug’ in one of her programmes — a moth that had been attracted by the warmth of the machine and had lodged itself in the computer’s mechanisms, causing it to malfunction. The term ‘bug’ had been used before to describe a problem in a computer programme but after Hopper’s logbook entry, the term really took off.

Nowadays computers aren’t exposed enough to attract these kinds of creatures, but we’re still on the lookout for pesky problems which we call bugs.

So, what is a bug?

A ‘bug’ refers to a problem that causes our programme to produce inconsistent or incorrect results. A bug can come from a variety of sources:

  • Hardware
  • Operating system
  • Networks
  • Tooling
  • Software

Bugs don’t necessarily completely break your programme. They might be hard to spot, or only happen occasionally or under certain conditions.

It’s important to know that a bug is not necessarily the same thing as an error. We’ll talk more about errors in a moment!

Bugs in Software

We’ll focus on bugs in software in this article. Bugs in software can be caused by a variety of things. Here are some examples:

  • Logic Failures: Your programme does something, but the logic isn’t quite correct. For example, you’ve got a queue of people to send emails to and your programme goes through and sends everyone an email, but you forget to remove people from the queue once they’re done. The programme appears to work the first time, but the second time you run it, it resends emails to the people who you forgot to take off the queue.
  • Edge cases: You have a function that formats data from a form, but you forgot to test what your function does if someone doesn’t fill in all the fields. As a result, you try to calculate something using some data that isn’t available and you get some unexpected result like "We have set up your new account, undefined Smith" .
  • Miscommunication: You built a signup form to accept a username and password because you thought that’s what was agreed in the meeting, but the edit account page has been built as though a user has an email and password, and nobody noticed the discrepancy.
  • Third party software: In the JavaScript world we use a lot of third party tools and software to build our apps, and any problems introduced into these can cause problems in our code. Any of the bugs that you might introduce into your own software could, of course, be introduced into poorly built or poorly maintained third party software.
  • Versioning problems: A version of third party software you’re using updates, you don’t realise, and it doesn’t work exactly the same as it did before. This produces unexpected behaviour in your code.
  • Poor testing: You haven’t got all your code tested so you introduce a change somewhere which has repercussions elsewhere in your code. For example, you update a function which you thought was only being used in one file, but you were actually using it as a helper function in another place too and now the modifications you’ve made mean it doesn’t work properly there.
This little bug wants to ruin your lovely function

Avoiding Bugs

Reading through the above list of causes naturally throws up some pretty obvious ways you can avoid bugs in your programme. Just to clarify:

  • Make sure you have as much test coverage as you can — both unit tests and integration tests. Testing not only helps us write better code in the first place, but helps protect us against breaking things when we refactor or add to our code later on.
  • Be sure to check that any third party software you’re using is from a trusted source and is regularly being maintained.
  • When it comes to logic problems, which is probably the number 1 source of bugs, it can help to talk your thinking through with a pair (or with an inanimate object, which is called rubber ducking). Writing your logic down in pseudocode before implementing it also helps, and again make sure your code is well tested. Testing forces you to express your ideas in plain language (e.g. “It should remove the email recipient from the queue”), meaning you’re more likely to spot any logic failures.
  • Use tools that encourage best practices such as a linter. Be strict with yourself with your settings to force yourself to write clean, readable code and immediately fix all warnings or errors your tooling feeds back to you. Frameworks such as React give you warnings if you’re doing something that it considers bad practice — heed the advice and save yourself a headache late.
This moth returns undefined

10 Tips for Tracking Down Bugs

Now for the fun bit! You’ve spotted a bug in your programme, now what? Tracking down and fixing bugs is an art, not a science. What works one day might not work the next, but over time you’ll get much better and develop an ‘intuition’ for it. Here are some tips for debugging. You might use one, several or all of these techniques tracking down a single bug.

  1. What kind of bug is it? Do you have an error message? If so — read it! (See the Errors section of this article)
  2. Let’s say you don’t have an error message, you just have unexpected behaviour. Make sure you can reproduce it — if you can’t then you’re going to have a hard time figuring out what’s going wrong.
  3. Ask yourself the question: What did I expect my code to do, and why? If you can, write a test for the expected behaviour. It’s easier to reason about a bug if you have a clear idea how you expected your code to behave and it the test will help you prove you’ve fixed it.
  4. Then ask: Whereabouts, roughly, do you think the problem’s coming from? You’ll find you can immediately discard lot of your code, which cuts the problem down.
  5. Go to the part of the code you suspect. Check for common mistakes: typos, forgetting to return from a function, failed imports or exports, unsaved files, that sort of thing.
  6. Still no luck? Time to do some detective work and figure out what’s going on in your code. If you’re used to using a debugger, great, but a more crude and often effective approach is to sprinkle some console.log statements around so you can visualise what’s happening as your code runs and to see where it breaks.
  7. Work backwards — or forwards. If your function returns NaN when you expected a number, work back from the end or forwards from the beginning to figure out the first moment at which something unexpected starts happening. That’s the area you need to hone in on.
  8. Work in an iterative approach. You might see 5 things that you suspect could be causing the bug but it’s important to just change a single thing at a time. If you change everything at once you won’t know what fixed it, you won’t learn anything and you might even have introduced a new bug at the same time. Plus, it will be harder to undo what you just did if it doesn’t fix the bug after all.
  9. Reason. If something’s not working it’s tempting to just change random numbers around, try moving things here and there, and basically just guessing or following some vague instinct. This occasionally works but usually doesn’t. Instead, ask yourself why you think a specific change will improve things and if you don’t know, the chances are it won’t.
  10. Get continual feedback. Run your tests or try to reproduce the bug every time you try something, otherwise you’re just coding in the dark. You might fix the bug before you realise, or you might find something else unexpected happens that can shed more light on what’s going on.
Error! Error!

Errors in JavaScript

I mentioned earlier that bugs aren’t necessarily errors. Let’s first of all just clarify what we mean by ‘error’.

In normal language when we talk of an error we simply mean that something went wrong or gave an unexpected result. Of course, plenty of bugs are errors in this sense of the term. Even a bug that doesn’t completely break your program or that doesn’t look like an error is, by this definition, an error.

But when we talk of errors in a JavaScript context, we often mean something like this:

Ahhhhhhh!

Errors are values thrown out by your program. An error will stop the execution of your program and that is not good.

However, errors are actually great because they tell you useful information about what’s going wrong. The key is not to panic and to calmly read the error message. Here in Node, I can see the line number and the file that caused the error, I can see that it’s a syntax error, and I can even see with little arrows (^^^) where exactly the problem occurred. Brilliant, eh?

Let’s just clarify what types of error you might encounter in JavaScript (remember, these are things that break your program. You will see them in the console of wherever you are running your code).

Syntax Error: Caused by missing closing brackets, that kind of thing. These errors occur as soon as you try to run your code. Nothing in your program will happen if you have a syntax error because the JS interpreter can’t understand your code. For example:

for (var key in person {    // SyntaxError: unexpected token {
console.log(person[key]);
}

ReferenceError: Caused by trying to do something with a variable that doesn’t exist. For example:

var a = 'cat';
console.log(b); // ReferenceError: b is not defined

TypeError: Caused by trying to do something to a value that can’t be done with that type of value. For example, trying to invoke a number.

var a = 9;
a(); // TypeError: a is not a function

RangeError: Caused by trying to do something that’s outside a permissible range of values. Common when using recursion and you add too many functions to the call stack. E.g. consider this code:

function sum (a) {
sum(a - 1);
}
sum(10);

Where does this code stop? Nowhere! It calls itself infinitely — or until JavaScript says nope and gives you a RangeError for exceeding the maximum number of functions it can deal with at any one time:

InternalError: I can’t remember seeing this one myself but it can apparently be thrown when an internal error in the JS engine occurs.

Understanding errors is a really important step in becoming an expert debugger, so if you encounter an error in your program think yourself lucky, read the message carefully and Google it if you don’t understand. 9 times out of 10, someone’s seen the error before, no matter how obscure it sounds, and there’s a stack overflow answer sitting out there waiting for you! 🙂