Search justacoding.blog. [Enter] to search. Click anywhere to close.

November 17th, 2021

21 Common JavaScript Mistakes To Avoid

JavaScript is a fairly quirky language, and as such, there are many pitfalls to be avoided when working with it. This is especially true when first starting out.

We’ve compiled a list of 21 of the most common JavaScript mistakes.

In this article, mistake refers to: something done incorrectly that almost certainly can (or will) lead to bugs in your code; or something done in a sub-optimal manner that can be easily improved upon with little effort.

Amongst the mistakes listed, some are fairly east to spot and rectify. Others, however, can be a little more elusive.

Let’s get started.

1. The dreaded NPE (or NullPointerException)

This is the grand-daddy of coding bugs in general. In programming, a NullPointerException occurs when you attempt to do something like this:

const myVariableInLowerCase = myVariable.toLowerCase()

Where myVariable is undefined or null.

JavaScript won’t actually throw a NullPointerException technically speaking — though this is what this type of error is commonly referred to as (or NPE, for short).

In JavaScript, this type of erroneous operation will throw a TypeError (if in Chrome, anyway, it can differ per browser) stating that you Cannot read properties of undefined.

It’s easy to avoid these type of errors by simply checking the type (or the existence) of the object or variable before invoking some method upon it:

if (typeof myVariable === "string") {
  myVariableInLowerCase = myVariable.toLowerCase()
}

You can only use the toLowerCase() on strings. To verify that myVariable is indeed a string (and hence, it actually exists/isn’t null or undefined) you can use typeof, as demonstrated here.

If myVariable wasn’t set, it’s typeof would be undefined and we wouldn’t attempt to invoke toLowerCase upon it at all.

This is a very common JavaScript mistake, but it’s not limited to JavaScript. You’ll see this type of issue across many other programming languages, too.

2. Comparing two objects incorrectly

In JavaScript, comparing objects is not as trivial as using the equals operator directly:

const firstPerson = { name: "Edward", age: 44 }
const secondPerson = { name: "Edward", age: 44 }

console.log(firstPerson === secondPerson) // false!

Comparing objects in this manner is definitely an easy trap to fall into, but unfortunately object comparisons don’t work this way in JavaScript.

In this case, === is checking if both objects refer to the same object — and not if all of the attributes are equal between the two objects. Which is probably what you are expecting and/or trying to do.

To compare object attributes, there’s not really an out of the box (native) solution unfortunately, so you’ll need to implement a custom approach instead.

If you’re already using a library like lodash or underscore, however, you’ll already have this mechanism at your disposal (in the form of the isEqual method, in both cases).

3. Thinking you’re modifying the original item when you aren’t

Some operations modify the original item, other’s don’t.

Let’s use filter in our example:

const fruits = ["apple", "orange", "plum"]

fruits.filter(fruit => fruit !== "orange")

console.log(fruits) // ["apple", "orange", "plum"]

orange is not removed from the original array because filter returns a new array.

If unfamiliar, it’s easy to expect filter to modify the original array. There are many other examples in JavaScript just like this one.

You just need to be mindful of what is returned by the functionalities you are using. This one is a common JavaScript mistake, but one that is fairly easy to avoid, too.

4. Using setInterval instead of setTimeout (and vice-versa)

It’s easy to use these two timing methods incorrectly without even realizing it.

Remember, setInterval performs some functionalities repeatedly at specific intervals in time, until it is stopped.

setTimeout performs it’s functionality only one time after a given interval has passed.

Similarly, a related JavaScript mistake is to use clearInterval on a timeout or clearTimeout on an interval.

5. Expecting Array.push to return an array with the new value included

If you aren’t sure, it’s always best to verify what is actually returned by the operation you are performing.

For instance when using push or unshift on an array:

const myArray = []
myArray.push("orange") // 1
myArray.push("blue") // 2
myArray.unshift("red") // 3

This returns the length of the new array.

You may naturally assume that push would return the array with the new item included, or the index of the inserted element — but this is not the case.

6. Not understanding strict vs loose comparisons

Here’s another very common pitfall. As far as JavaScript mistakes go — this one’s undoubtedly a classic.

Not understanding the difference between double-equals (==) and triple-equals (===).

Double-equals performs a “loose” comparison. So the two items you are comparing can still be considered equal in this comparison even if they are of differing types:

console.log(33 == "33") // true
console.log(true == "true") // true

This is because JavaScript firstly coerces each type before this evaluation (to the same type, if possible).

With triple-equals, this is not the case. The items must also be of the same type, so it’s much stricter in that regard:

console.log(33 == "33") // false
console.log(true == "true") // false
console.log(12 === 12) // true

You’ll definitely want to favour using triple-equals where possible unless you have a specific reason to do otherwise.

It’s well worth in reading up on all of various the nuances in this regard.

7. Not understanding this

JavaScript mistakes caused by an improper (or lack of) understanding of this and how it works are definitely fairly common.

The concept of this can be fairly daunting to beginners, and this likely contributes to a lot of the pitfalls in this regard.

The main issue is that this can (or more specifically, does) refer to different things depending on where it’s used.

For example, used out in the open this refers to the global object:

console.log(this) // [object Window] or something similar 

However, when this is used in the method of an object:

const car = {
  make: "BMW",
  mode: "M2",
  drive: function() {
    // "this" refers to the car object as this 
    // object is the owner of this function
    console.log(this)
  }
}

It instead refers to the owning object (car), not the global object anymore.

One of the issues here is it’s hard to even go about debugging these issues if you don’t have a familiarity or understanding of JavaScript’s this to begin with.

If you’re unfamiliar, I’d definitely recommend brushing up on the other various contexts that this can be used in.

8. Not understanding variable scope

Variable scope is an important mechanism/concept to understand within JavaScript — there are some nuances to this topic, too.

Here’s a common JavaScript mistake which is especially prominent among beginners:

if (shouldSetName) {
  const name = "Rupert"
  // or let name = "Rupert"
}
alert("Welcome, " + name) // ReferenceError - name isn't defined

This will lead to a ReferenceError as const and let variables in JavaScript are block-scoped.

In this case, name was initialized inside the if statement (block) — so it’s not accessible or visible outside of this particular block.

However, if you were to do this:

if (shouldSetName) {
  var name = "Rupert"
}
alert("Welcome, " + name) // no error, name is defined!

This would work as expected. This is because var variables variables are fundamentally different to const and let variables in this regard. var variables can be accessed outside of the block they were defined in.

This particular example demonstrates what is known as block scope.

There are two other types of scope in JavaScript: global scope and function scope. To read more about block scope as well as these other types of scope, I’d recommend this article in particular.

9. Concatenating (or adding) variables incorrectly

In JavaScript, + is used to add numbers together as well as to concatenate (join) strings.

This can lead to unexpected outcomes if you aren’t sure that the items you are concatenating (or adding) are of the same type:

const firstNumber = 20
let secondNumber = 25

console.log(firstNumber + secondNumber) // 45

// it got converted to a string somewhere, somehow...
secondNumber = "25"

console.log(firstNumber + secondNumber) // "2025"

This exposes one of the weaknesses of loosely-typed languages like JavaScript, and demonstrates how easy it is to fall prey to these kind of mistakes.

You may assume both firstNumber and secondNumber are both actual numbers, then add them together.

In that case, you’ll expect a numeric value in return. But one of the variables is, in fact, a string (secondNumber) — so you’ll get a string returned instead as JavaScript is actually concatenating both variables together instead of adding them as expected!

10. Not using try/catch

Let’s say you application parses some JSON.

You should be mindful that this parsing operation can throw errors, for example if invalid or incorrect input is encountered.

You can handle these kind of scenarios by wrapping the relevant code in a try/catch block, like so:

try {
  JSON.parse("I cannot be parsed")
} catch (e) {
  console.error("An error occurred, handle it accordingly...", e)
}

This will capture any errors that occur during the JSON.parse operation, and it’ll allow you to gracefully handle or react to these errors within the catch block.

For instance you can alert the user via the UI that they’ve supplied some invalid input, or something similar.

Many JavaScript mistakes can be avoided or controlled with this kind of forethought, knowing when errors are thrown and making sure to catch them.

11. Treating asynchronous code in a synchronous manner

When working in JavaScript, it’s vitally important to understand how asynchronous code works as opposed to synchronous code.

Here’s an example using the async/await approach:

const fakeNetworkRequest = () => {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log("resolved!")
      resolve("success")
    }, 2000)
  })
}

const changeName = async () => {
  const result = fakeNetworkRequest()
  // incorrect, we aren't awaiting the promise above
  console.log("Result received: ", result)
  console.log("I'll get logged before the promise even resolves...")
}

Here we’re using the async keyword when defining the changeName function.

However, we aren’t awaiting the resolution of the underlying promise within fakeNetworkRequest.

This means that the subsequent code (just the console.logs in this case) will likely execute before the promise has even been fulfilled.

So the mistake here is that the developer is expecting this code to run synchronously.

But that’s not how promises work.

const changeName = async () => {
  const result = await fakeNetworkRequest()
  // correct, the promise has been awaited...
  console.log("Result received: ", result)
  console.log("The promise has been resolved by the time I am logged")
}

If you await the promise, you’ll then be able to properly use the results of the promise (in this example, just assume some data has been fetched from the backend or something like that) as expected.

Not treating code that’s meant to run asynchronously in this manner can lead to a whole host of problems and unexpected behaviours!

12. Using map and forEach interchangeably

This one isn’t really a JavaScript mistake, per-se, but it’s worth pointing out in this list.

Consider the following example:

const people = [{ name: "Sarah", age: 40 }, { name: "Peter", age: 33 }]
const ages = []

people.forEach(person => {
  ages.push(person.age)
})

There’s nothing inherently wrong with this approach, but you can simplify and condense it a lot by instead using map:

const ages = people.map(person => person.age)

forEach performs some functionality for every item in the array.

map creates a new array directly based on the results of the provided function.

So it can be considered that there is some overlap between these two methods; but map is much more suitable for this particular use case. It means you don’t need to manually push each item into the new array (or initialize the new array separately).

13. Using let inappropriately

You should initialize your variables with const by default (not let) if you know that the variable in question won’t (or shouldn’t) need to be changed or modified.

This makes your code clearer to other developers and it also makes it less error-prone. The variable cannot be reassigned and thus you can guarantee later on that the variable has actually been initialized with a given type.

let name = "Anne"

// somewhere deep within your application...
// an accident happens
name = 123

// now when you try...
name.toLowerCase() // oops

14. Not using strict mode

Strict mode essentially makes your code less error-prone, and you should realistically always be using it where possible.

To enable safe mode in JavaScript, you can just prepend your JavaScript files with:

use strict;

As the name would imply, it’s a “stricter” mode. It’ll force you to write less buggy and more robust code.

One example of this is the way strict mode handles certain errors.

There are errors in non-strict mode JavaScript that can fail “silently”. So you won’t necessarily be notified that an error has even occurred.

In strict mode, these same errors would now throw exceptions instead.

This means that bugs that may have otherwise been awkward to catch, or may have even gone entirely unnoticed — these bugs will be apparent right away during the development phase, now.

15. Using hyphens in object property names

In comparison to the other JavaScript mistakes on this list, this one is likely not very common. But it’s probably still worth covering anyway.

JavaScript will raise a SyntaxError if you do this:

const myObject = { some-property: "A property" }

However, you can still do this:

const myObject = { "some-property": "A property" }

This isn’t recommended, however, as it can lead to numerous problems.

Let’s assume you want to access some-property, for instance:

console.log(myObject.some-property)

This will throw a ReferenceError stating property is not defined. The hyphen is interfering!

You can still actually access some-property using this notation, instead:

console.log(myObject["some-property"])

But it’s recommended to simply not use hyphens in property names to save any headaches later on!

16. Putting a lot of complex code in to one function

Strictly speaking, this isn’t necessarily a JavaScript mistake per-se. But it’s definitely a pitfall that many developers can fall into, so it’s worth covering here.

It’s better to have smaller, reusable functions which can be used as building blocks, not massive functions that contain lots of complex, intertwined business logic.

There are several benefits to keeping your functions small and modular:

  • The code in general is much easier to maintain as it’ll ultimately become less complex and less daunting
  • It’ll hopefully be easier to see where bugs are coming from and how to go about more effectively resolving these bugs
  • You can leverage existing code and reuse it instead of writing the same code over and over

If you have a function that includes some logic to break apart a string (somebody’s full name) and capitalize the first letter of each portion of this name, for example, this would be a classic case for a utility function.

The same utility function (formatName) could then be used freely and easily anywhere else across the codebase without any effort.

That’s a trivial example. But in general you should think with this mindset.

Keep you functions clean and try to give them very specific responsibilities.

17. Not discerning between arrow functions and “regular” functions – they are different!

It’s a common JavaScript mistake to assume that arrow functions and regular functions are technically identical, with only syntactical differences.

This is not the case, however.

There are several differences between these two types of function, the most prominent one is perhaps the behaviour of this:

// the global window object
const myFunc = () => console.log(this)

With arrow functions, this is inherited from the outer function. They don’t really have a this of their own.

This can be problematic for several reasons. Take this example:

const car = {
  make: "BMW",
  model: "M3",
  getMakeAndModel: function() {
    // "this" refers to the car object as this
    // object is the owner of this function
    console.log(this.make + " " + this.model) // BMW M3
  }
}

Now if you were to convert the getMakeAndModel method to an arrow function instead of a regular function:

const car = {
  make: "John",
  mode: "Doe",
  getMakeAndModel: () => {
    // "this" now refers to the global window object
    console.log(this.make + " " + this.model) // undefined undefined
  }
}

this refers to the global window object!

So as demonstrated, the global window object doesn’t contain the make and model properties we are looking for — they are undefined when we log them out.

This is only one of the key differences with regards to how arrow functions behave versus regular functions, but I’d say this is definitely the one that’s most likely to lead to mistakes in your JavaScript code!

18. Leaving in unwanted console.logs

If you’re working with other developers on production code, it’s likely that you won’t want console.logs leaving in place. There are exceptions to this, but generally, in production code, you won’t want the logs.

After debugging locally, be mindful of this, and remember to clear up those leftover logs!

19. Bad (or inconsistent) code formatting and style

Inconsistent code formatting or code style is not the end of the world, but it’s best to be consistent and keep everything neat and tidy across the board where possible.

This is especially true when working as part of a team. In this case, it’s definitely a good idea to implement and configure a code formatter like Prettier.

This integrates with your IDE or editor, and it means that all contributors can maintain a consistent code style. Prettier (and other tools like it) enforce a specific code style. This code style can of course be configured and tweaked by the team as a whole as per the agreed preferences.

This simply saves a lot of time and keeps the project neat and tidy. It also removes the requirement for one extra step (verifying that all of your brackets are lined up, for example, or verifying that there are no extra empty lines) before committing your code.

20. Accidentally assigning instead of comparing

This one’s short and sweet. But a JavaScript mistake most of us will no doubt encounter at one time or another:

let name = "Rachel"

if (name = "Tom") {
  console.log("Hi Tom!")
}

There’s a simple typo here. We’re using = instead of == (or more preferably, ===).

This means that in this case, the if statement will always evaluate to true, and subsequently Hi, Tom! will always be logged to the console.

Not only that, we’ve actually reassigned name (to Tom) now, also!

21. Forgetting about zero-indexing

JavaScript, like a lot of other languages, counts from 0. Not from 1, as some beginners may wrongly suspect.

So accessing the first item in the array can be achieved like so:

const colours = ["blue", "pink", "yellow"]
colours[0] // blue

And not like so:

colours[1] // pink

This would give us the second element, as JavaScript counts from 0.

In conclusion

And that’s the end of the list

We hope you’ve enjoyed our glimpse at these more common JavaScript errors, hopefully you’ve learned something new in the process!

There are 21 errors or mistakes here, but this is only scratching the surface.

Check back later for follow-up articles as well as other “common mistake” articles spanning different domains (React, Node and so on).

For other JavaScript related content, do feel free to check out out JavaScript category in the meantime.

Thanks for reading!

Have any questions? Ping me over at @justacodingblog
← Back to blog