How are devs coding with AI?

Help us find out-take the survey.

Common JavaScript Errors Explained and How to Fix Them

JavaScript errors occur when your code does something incorrect that the language doesn’t support. They range from simple undefined variable references, through to more complex errors relating to async code and object scopes.

Understanding different error types and their causes enables you to anticipate where problems could occur. Writing bug-free code first time around means you can increase development velocity and spend less time chasing issues. In this guide, we’ll cover some of the most common JavaScript errors and share best practices for debugging them.

Debugging Common JavaScript Errors

JavaScript errors can be frustrating to troubleshoot. New developers may be unsure what a specific error actually means, especially if it’s being triggered from deep inside affected code. Experienced developers can easily encounter problems too, whether due to typos or incorrect use of advanced coding patterns.

Effective JavaScript error debugging and prevention requires you to accurately analyze the causes of problems. Beyond understanding different JavaScript error types, it’s useful to try intelligent tools like Qodo Gen that can explain errors for you. Qodo Gen lets you copy error messages into a chat conversation, then receive a contextually relevant response. Qodo’s AI agent understands your project’s code so it can provide a precise explanation of why each error is occurring.

Let’s now explore some of the top JavaScript error types.

Syntax Errors

Code written without the correct syntax will trigger syntax errors, which are among the most basic and common JavaScript errors.

Here are some examples.

Function with Missing Opening Curly Brace

Error

// SyntaxError: Unexpected token 'return'
function sum(a, b) 
	return (a + b);
}

Correction

function sum(a, b) {
	return (a + b);
}

Missing Mathematical Operator

Error

// SyntaxError: Unexpected token 'return'
function sum(a, b) 
	return (a  b);
}

Correction

function sum(a, b) {
return (a + b);
}

If Condition With Missing Closing Parentheses

Error

// SyntaxError: Unexpected token '{'
if (test {
	console.log("OK");
}

Correction

if (test) {
	console.log("OK");
}

Fixing JavaScript Syntax Errors

Syntax errors are frequently caused by parentheses, curly braces, and mathematical operators that are missing or positioned incorrectly. These errors typically result in large blocks of code being incorrectly formatted. The error message will usually point to the line of code where the issue is present, but it may not always be accurate. To fix these errors, carefully examine your code for these types of mistakes and add the necessary characters in the appropriate places.

Type Errors

JavaScript type errors occur when you perform an unsupported operation on a value:

// TypeError: User.username is not a function 

const User = { 
    username: "Demo" 
}; 

User.username();

In this example, the user object has a property called username, but it’s set to a string, not a function. As a result, a TypeError is thrown when the code tries to call username() as a function.

To fix type errors, begin by verifying that you are accessing the correct variable. Next, examine where the variable is assigned to confirm that it always has the expected type. IDEs and static analysis engines can also identify these issues.

Reference Errors Caused by Undefined Values

You’ll see a JavaScript Reference Error when you try to access a variable that hasn’t been defined:

// ReferenceError: User is not defined
console.log(User.username);

This code doesn’t set the User variable so a ReferenceError occurs when it’s accessed. To fix the problem, make sure the variable is always defined by the time you use it:

const User = {

	Username: "demo"

};

console.log(User.username);

This problem often occurs when a variable is set conditionally in an if statement. If you’ll use the variable after the if statement, make sure you assign it a default value either outside the block or within an else statement.

Range Errors

Some JavaScript operations involving iteration, recursion, arrays, and mathematics throw a RangeError when you try to access an invalid position. It means the position is out of range because no value has been assigned.

Trying to create an array with a negative length will always trigger a RangeError:

// RangeError: Invalid array length
const arr = new Array(-5);

Similarly, a RangeError is thrown when you pass an invalid value to mathematical functions like toFixed():

// RangeError: toFixed() digits argument must be between 0 and 100
const pi = 3.54;
console.log(pi.toFixed(-100));

The best way to fix range errors is to modify your code to always keep values within the valid range. Make sure to check values from user input and, if they could result in a range error, reset them to fall within the allowed bounds. The following example demonstrates a safe way to handle this.

const numberOfUsers = getNumberOfUsers();

if (isNaN(numberOfUsers) || (numberOfUsers < 0)) {
	numberOfUsers = 0;
}

// No RangeError because numberOfUsers is always a valid argument
const usersArray = new Array(numberOfUsers);

Scope Errors

You should correctly scope each of your variables to avoid unexpected errors. In JavaScript, variables can be declared using  let, var, or as a global variable. It is essential to understand the scope associated with each declaration to prevent errors.

  • let declares a block-scoped variable, accessible only within the nearest enclosing block (defined by curly braces {}).
  • var declares a function-scoped variable, accessible throughout the entire function where it is declared
  • Global variables are declared by omitting let, var, or const, or by using var outside a function at the global scope.  These variables can be accessed from anywhere within your script.
  • Mishandling variable scope can lead to unexpected behavior. Ensure that you declare your variables with the appropriate scope for your use case.

You should correctly scope each of your variables to avoid unexpected errors:

var message = "nothing to report";

function sayHello(user) {

	message = "Hello";

	console.log(message);

}

// nothing to report
console.log(message);

// Hello
sayHello();

// Hello
console.log(message);

The example above demonstrates the pitfalls of scope. The sayHello() function doesn’t apply var or let to its message variable, so the value assignment applies to the variable defined in the global scope. The issue can be fixed by correctly scoping the variable within the function body:

var message = "nothing to report";

function sayHello(user) {

	var message = "Hello";

	console.log(message);

}

// nothing to report
console.log(message);

// Hello
sayHello();

// nothing to report
console.log(message);

Now a new variable is defined within the function’s scope.

This Errors

JavaScript’s this keyword refers to the context in which the code is running. It can vary depending on how a function is called. This often causes problems when this doesn’t reference the context you expect it to:

const User = {

	username: "demo",

	getUsername() {
		return this.username;
	}

}

const getUsername = User.getUsername;

// undefined
console.log(getUsername());

This example displays undefined because the getUsername() function is being called in the context of the global scope, not the User object. You can solve the problem by either calling the method directly on User, or explicitly binding it to the User object.

// Option 1
// demo
console.log(User.getUsername());

// Option 2
// demo
console.log(getUsername.call(User));

// Option 3
const getUsername = User.getUsername.bind(this);

// demo
console.log(getUsername());

Strict Mode Errors

JavaScript’s strict mode helps eliminate common errors by forbidding potentially problematic operations. It enforces best practices and prevents use of syntax that’s known to cause issues.

You can opt into strict mode at the function or file level:

"use strict";

function myFunction() {
	"use strict";
}

There’s significant variations in JavaScript’s behavior depending on whether strict mode is enabled for a particular line. Because it’s possible to mix and match strict and non-strict functions and files, this can lead to confusing results when errors are either thrown or silenced unexpectedly.

function functionOne() {
	// Assigning to an undefined variable is forbidden in strict mode
	message = "demo";
}

function functionTwo() {
	"use strict";
	functionOne();
}

functionTwo();

The code above runs successfully: functionOne() performs an operation that’s forbidden in strict mode, but strict mode isn’t actually enabled for the function. Although functionTwo() does enable strict mode, this only applies to its own code—not the code in functionOne(). It’s therefore critical that strict mode is correctly enabled for relevant files and functions. For the most predictable results, try to apply it across your entire application.

Event Handling Errors

JavaScript’s browser event handling system can hide subtle errors that prevent your handlers from working correctly.

The first common problem is trying to access an event’s details without explicitly declaring an event argument in your handler:

document.addEventListener("click", () => {
	// ReferenceError: event is not defined
	console.log(event.target.getAttribute("name"));
});

This code still works in many browsers, but is deprecated and can cause confusion. It’s good practice to explicitly define an event argument instead:

document.addEventListener("click", event => {
    // ReferenceError: event is not defined
    console.log(event.target.getAttribute("name"));
});

Event handlers can also suffer from scope confusion, especially when combined with modern JavaScript syntax features like arrow functions:

document.addEventListener("click", () => {
	// TypeError: this.getAttribute is not a function
	console.log(this.getAttribute("name"));
});

The this keyword normally refers to the DOM element that the event was fired on, but this doesn’t apply to arrow functions. They don’t create their own scope, so this actually refers to the outside scope surrounding the arrow function’s definition. You can fix this by switching to a regular function definition:

document.addEventListener("click", function () {
	// Works OK (this is the element that fired the event)
	console.log(this.getAttribute("name"));
});

Many event handling problems stem from unwanted event propagation. By default, events “bubble up” through the DOM elements above the element on which they’re fired. If you’ve registered multiple event handlers in the DOM tree, then you could find that several handlers fire for one event. Use the event object’s stopPropagation() method to confine the event and stop it bubbling up beyond the current handler.

document.addEventListener("click", event => {
	// Parent elements won't be notified of the event
	event.stopPropagation();
});

Circular References and Internal Recursion Errors

JavaScript code often involves object structures, closures, and functional programming methods that can be susceptible to circular references. This may lead to infinite internal recursion that ends with a RangeError or InternalError, depending on the JavaScript engine being used.

If a function calls itself, or a property in an object refers back to the same object, then you should ensure there’s an escape mechanism that prevents the recursion descending too far. JavaScript monitors the size of the function call stack and stops your script if it becomes too deep (the limit varies by engine).

Recursion errors can also crop up when you perform operations on an array that includes itself:

class User {

	constructor(username) {
		this.username = username;
	}

}

const users = [];
users.push(new User("demo"));
users.push(users);

const listUsernames = () => {

	for (const user of users) {
		if (Array.isArray(user)) {
			listUsernames(user);
		}
		else console.log(user.username);
	}

};

listUsernames();

Here, the nested loop never terminates because the user variable continually refers back to the same array.

Unexpected Results from Async Functions

Asynchronous code can be particularly susceptible to errors. The most common mistake is forgetting to use the await keyword when calling an async function:

async function fetchUsers() {
	// ...
}

const users = fetchUsers();

// Promise { <pending> }
console.log(users);

Because this code doesn’t use await, JavaScript won’t wait for the fetchUsers() call to complete. The value of users won’t resolve to the final value, causing unexpected behavior in your app. You can fix this by ensuring all async functions are called with await:

async function fetchUsers() {
	// ...
}

const users = await fetchUsers();

// Final value
console.log(users);

Unhandled Promise Rejection Errors

UnhandledPromiseRejection errors occur when a Promise without a .catch() handler calls reject() to report an error:

// UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch()
const fetchUsers = new Promise((resolve, reject) => {
	reject("Not implemented");
});

To fix this error, you must chain a .catch() handler onto the Promise. Your function will receive the thrown error as an argument, allowing your script to gracefully recover from the problem.

// "An error occurred"
const fetchUsers = new Promise((resolve, reject) => {
	reject("Not implemented");
}).catch(e => {
	console.error("An error occurred.");
});

Use of Reserved Identifiers

JavaScript reserves several keywords. Reserved keywords cannot be used to name variables, functions, classes, and other code entities.

Some of these words—like case, class, import, new, and package—are often good fits for values used within an application. However, trying to use them as a JavaScript name will trigger a SyntaxError. The JavaScript engine will still try to interpret them as language features:

const ImportFactory = () => {
	return {importType: "demo"};
};

// SyntaxError: Unexpected token 'import'
const import = ImportFactory();

You can avoid these errors by simply ensuring you don’t use the reserved keywords to name any variables, functions, or classes. Static analysis engines and AI coding assistants can help reveal naming problems.

JavaScript Module Errors

JavaScript modules allow you to import code from other scripts. They’re supported in browsers and standalone engines like Node.js:

// lib.js

const sum = (a, b) => (a + b);

export {sum};

// main.js

import {sum} from "./lib.js";

console.log(sum(3, 2));

Import Declarations May Only Appear at Top Level

Receiving an import declarations may only appear at top level of a module error (or a similar message) is one of the most common module errors. It’s usually seen in browsers when you’ve forgotten to mark the script as a module when loading it in a <script> tag:

<head>
	<!-- The script will not be loaded as a module -->
	<script src="main.js"></script>
</head>
You can correct the error by adding the type="module" attribute to your <script> tag:
<head>
	<!-- The script will now be correctly loaded as a module -->
	<script src="main.js" type="module"></script>
</head>

You can correct the error by adding the type="module" attribute to your <script> tag:

<head>
	<!-- The script will now be correctly loaded as a module -->
	<script src="main.js" type="module"></script>
</head>

If you’re using Node.js, then you should rename your script to have a .mjs file extension. Alternatively, you can set "type": "module" in your package.json file instead.

This error can also occur if you try to use the import statement within a nested function or code block.

Requested Module Does Not Provide an Export Named …

Another common module error is the requested module <module> does not provide an export named <export>:

// SyntaxError: The requested module './lib.js' does not provide an export named 'subtract'
import {subtract} from "./lib.js";

console.log(subtract(3, 2));

The error’s thrown in this example because lib.js doesn’t export anything called subtract. Check your import statement for typos, then ensure the item you’re trying to import is specified in the module’s export statement.

Export … Is Not Defined in Module

This error is the counterpart to the previous one discussed above. It occurs when an export statement references an undefined variable:

const sum = (a, b) => (a + b);

export {sum, subtract};

There’s no variable called subtract in the scope so it can’t be exported from the module.

How to Prevent JavaScript Errors?

While coding JavaScript programs, encountering bugs and errors is unavoidable. However, many of these can be prevented by adopting safe programming practices and utilizing modern tools for early detection of potential issues during code development.

Write Comprehensive Unit Test Suites

Unit tests ensure your code functions as intended before going live, allowing for rapid iteration on isolated units of logic and verifying that everything remains functional after future updates. Though writing tests can be time-consuming and tedious, generative AI tools like Qodo Gen automate the process by intelligently analyzing your project to understand how the code operates.

Use Linters and Static Analysis

JavaScript linters and static analysis tools like ESLint and Semgrep scan your code without running it. They report bugs, errors, and security vulnerabilities, providing instant feedback to developers.

Use Linters and Static Analysis

Generative AI lets developers automate code production, eliminating errors caused by typos and human mistakes. It ensures best practices are followed so your code works more efficiently and contains fewer bugs.

Qodo Gen is an IDE extension that provides advanced context-aware gen AI code completion and chat. Adopting Qodo enables you to rapidly generate new code that’s tailored to your project and fully aligned with your team’s code quality standards.

Use Generative AI to Rapidly Produce Effective Error-Free Code

Using TypeScript instead of JavaScript also prevents many common errors by enforcing strict type safety. TypeScript code looks like JavaScript and compiles back to it, but supports advanced syntax including interfaces, enums, decorators, and mixins. It can make larger JavaScript applications much more maintainable.

Summary

JavaScript applications are susceptible to errors because the language is loosely typed and doesn’t enforce strict practices by default. Moreover, its object model can create confusing situations with the this keyword, while many modern JavaScript programs also rely on asynchronous code that has unique error handling requirements.

In this article, we’ve discussed some of the most common causes of JavaScript errors, but this is far from being an exhaustive guide. You can keep your JavaScript projects free of errors by using Qodo Gen to intelligently generate error-free code and tests that match your project’s standards. Book a demo or get started for free.

Start to test, review and generate high quality code

Get Started

More from our blog