Throw & Re-Throw Runtime Errors in JavaScript

18 min read·Jan 14, 2026

In JavaScript, runtime errors can also be triggered programatically on purpose, when the code detects an invalid situation that would prevent it from completing its tasks properly or safely.

Throwing errors allows the code to stop its normal execution flow immediately and signal that something went wrong, like for example, an invalid input or an illogical state. Once an error is thrown, JavaScript will jump out of the current code path and look for a place where that error is handled.

In this lesson, you'll learn how to throw various runtime errors and how to bubble them up through multiple function calls.

Throw runtime errors

In JavaScript, throwing (or raising) errors allows developers to control the flow of an application in the presence of exceptional conditions or unexpected situations.

Errors can be thrown for a multitude of reasons, such as indicating exceptional conditions, preventing further execution of faulty code, enforcing contracts and invariants within your code, and so on.

To raise an error, you can use the throw keyword followed by an instance of the Error class or one of its subclasses:

throw new Error('error message');

Beyond the Error class itself, JavaScript has several built-in error types that all inherit from it, including but not limited to:

  • SyntaxError to indicate a syntax error.
  • TypeError to indicate an operation was performed on a value of an inappropriate type.
  • RangeError to indicate a numeric value is out of range.
  • ReferenceError to indicate an invalid reference.
  • EvalError to indicate an error regarding the eval function.

💡 In JavaScript, the throw keyword can be used to throw any type of data, including strings, numbers, objects, and so on (e.g., throw 3).

It is however considered good practice to only throw instances of the Error class or its subclasses, as it provides a standard structure for error handling and integrates better with debugging tools.

Example

Let's consider this script:

function divide(a, b) {
  if (isNaN(a) || isNaN(b)) {
    throw new TypeError('Operands must be integers or floating-points');
  } else if (b === 0) {
    throw new RangeError('Cannot divide by zero');
  }
  return a / b;
}

try {
  console.log(divide('a', 3));
} catch(error) {
  console.error(error.toString());
}

try {
  console.log(divide(6, 0));
} catch(error) {
  console.error(error.toString());
}

try {
  console.log(divide(10, 2));
} catch(error) {
  console.error(error.toString());
}

💡 The instances of the Error class have a toString() method that allows you to only output the error name and error message.

When executed, it will:

  1. Define a divide() function that:

    • Throws a TypeError if one of its parameters is not a valid number.
    • Throws a RangeError if its 2nd parameter equals 0.
    • Returns the result of the division of the 1st parameter by the 2nd parameter.
  2. Attempt to execute the divide() function wrapped in a try...catch block, and either output its return value or the error thrown by the function, using as arguments:

    1. A character ('a') and a positive integer (3).
    2. A positive integer (6) and 0.
    3. Two positive integers (10, 2).

Which will produce this output:

TypeError: Operands must be integers or floating-points
RangeError: Cannot divide by zero
5

Bubble up errors

In programming, bubbling up errors or letting errors through refers to the process of allowing an error to propagate up the call stack until it is caught by an appropriate error handler.

This technique is often used in complex, multi-layer applications to help prevent the implementation of per component error-handling logic, which would ultimately make any application unscalable due to a lot of redundancies in the code.

It allows developers to centralize errors into a generic error-handling component in charge of executing the appropriate logic according to context.

Example

Let's consider this script, that extracts the email addresses from a CSV-formatted string:

// Throw an error if the provided email address is not valid
function validate(email) {
  const emailRegex = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/;

  if (!emailRegex.test(email)) {
    throw new Error(`Invalid email address: "${email}"`);
  }
}

// Extract email addresses from a CSV-formatted string
function parse(csv) {
  const lines = csv.trim().split('\n').slice(1);
  let emails = [];

  for (let line of lines) {
    const email = line.split(',').slice(2);
    validate(email);
    emails.push(email);
  }

  return emails;
}

// Handle parsing and validation errors
function process(csv) {
  try {
    console.log(parse(csv));
  } catch(error) {
    console.log(error.toString());
  }
}

const data = `
last_name,first_name,email_address
Smith,John,john.smith@example.com
Doe,Jane,jane.doe@example.com
Brown,Charlie,charlie.brown
Johnson,Emily,emily.johnson@example.com
Williams,Michael,michael.williams@example.com
`;

process(data);

When executed, the parse() function will let the error thrown by the validate() function through to be handled by the try...catch block defined in the process() function.

Which will produce this output:

Error: Invalid email address: "charlie.brown"

Re-throw errors

Unlike bubbling up errors, re-throwing errors refers to the process of catching errors in the lower levels of the application and enriching them with additional debugging information before forwarding them to the upper layers.

This allows the higher levels to better understand the context in which these errors are raised and to centralize their handling.

To add context to an error, you can use the cause property of the object passed as second argument to the Error constructor function:

new Error(message, { cause? })

Where:

  • cause is an optional property used to indicate the reason why the current error is thrown, which usually contains the caught original error.

Example

Let's consider this modified version of the previous script:

// Throw an error if the provided email address is not valid
function validate(email) {
  const emailRegex = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/;

  if (!emailRegex.test(email)) {
    throw new Error(`Invalid email address: "${email}"`);
  }
}

// Extract email addresses from a CSV-formatted string
function parse(csv) {
  const lines = csv.trim().split('\n').slice(1);
  let emails = [];

  for (let i = 0 ; i < lines.length ; i++) {
    const email = lines[i].split(',').slice(2);

    try {
      validate(email);
      emails.push(email);
    } catch(error) {
      // Catch and re-throw the error
      throw new Error(`Error at line: ${i + 2}`, { cause: error });
    }
  }

  return emails;
}

// Handle parsing and validation errors
function process(csv) {
  try {
    console.log(parse(csv));
  } catch(error) {
    console.log(error.toString());
    console.log(error.cause.toString());
  }
}

const data = `
last_name,first_name,email_address
Smith,John,john.smith@example.com
Doe,Jane,jane.doe@example.com
Brown,Charlie,charlie.brownexample.com
Johnson,Emily,emily.johnson@example.com
Williams,Michael,michael.williams@example.com
`;

process(data);

When executed, the parse function will catch any error thrown by the validate function and re-throw a new error to the process function containing the line number the parsing error occurred at, as well as the original error caught by the catch block.

Which will produce this output:

Error: Error at line: 4
Error: Invalid email address: "charlie.brownexample.com"

Enjoying the courses?

I've made these courses completely free so anyone can learn from them. If they've helped you and you'd like to actively support the work behind BackendBrewery, you can leave a tip:

Support BackendBrewery
Throw & Re-Throw Runtime Errors in JavaScript | Backend Brewery