JavaScript Error Types: Error, TypeError, and RangeError

JavaScript uses built-in error types to describe what went wrong when code fails at runtime or when you intentionally signal a problem. Understanding Error, TypeError, and RangeError helps you read stack traces faster, choose better exceptions, and write code that fails in a useful way.

Quick answer: Use Error for general failures, TypeError when a value has the wrong type or shape for an operation, and RangeError when a value is the right type but outside the allowed range.

Difficulty: Beginner

You'll understand this better if you know: basic JavaScript variables, functions, and how to read a simple stack trace.

1. What Is JavaScript Error?

JavaScript error types are standard objects that represent failures. They all describe problems, but each one points to a different kind of issue.

When code throws one of these errors, JavaScript stops the current flow unless you catch it with try...catch.

2. Why JavaScript Error Types Matter

These error types make debugging and API design much clearer. If you throw the right error, other developers can understand what went wrong without digging through implementation details.

They also help you write better validation. For example, if a function expects a number between 1 and 12, using RangeError tells the caller that the type may be correct, but the value is not allowed.

In real projects, these distinctions matter when you:

3. Basic Syntax or Core Idea

All three error types are created with new. You can throw them directly or catch them after they are thrown.

Creating and throwing an error

The simplest pattern is to create an error instance with a message, then throw it.

throw new Error("Something went wrong");

This creates an exception object and stops normal execution until the error is handled.

Catching an error

You can catch the thrown error and inspect its name and message.

try {
  throw new TypeError("Expected a function");
} catch (error) {
  console.log(error.name);    // TypeError
  console.log(error.message); // Expected a function
}

The name property identifies the error class, while message explains the failure in human-readable form.

4. Step-by-Step Examples

Example 1: General failure with Error

Use Error when the problem is real but does not fit a more specific built-in type.

function loadConfig(config) {
  if (!config) {
    throw new Error("Config is missing");
  }

  return config;
}

This is a good default when the caller needs to know that something failed, but the failure is not specifically about type or range.

Example 2: Wrong type with TypeError

Use TypeError when a value exists but cannot be used as expected.

function repeatName(name) {
  if (typeof name !== "string") {
    throw new TypeError("name must be a string");
  }

  return name + " " + name;
}

This pattern is common in utility functions and public APIs because it gives an immediate, precise signal about bad input.

Example 3: Out-of-range value with RangeError

Use RangeError when the input type is correct, but the value is not allowed.

function getMonthName(monthIndex) {
  if (monthIndex < 0 || monthIndex > 11) {
    throw new RangeError("monthIndex must be between 0 and 11");
  }

  return ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
          "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][monthIndex];
}

The type is correct because monthIndex is a number, but the allowed range is limited to valid month positions.

Example 4: Reading built-in errors from the platform

You do not always create these errors yourself. JavaScript and the browser also throw them for you.

try {
  JSON.parse("not valid JSON");
} catch (error) {
  console.log(error instanceof Error); // true
  console.log(error.name); // SyntaxError
}

Even though this example uses another built-in error type, it shows the same pattern: inspect the error object, then decide how to respond.

5. Practical Use Cases

A typical use case is a public function that accepts a number, string, or object and needs to fail early when the input is wrong.

6. Common Mistakes

Mistake 1: Using TypeError for every problem

Beginners often throw TypeError even when the issue is not about type. That makes error messages less accurate and can confuse people reading logs.

Problem: This code throws a type error for a value that is simply out of range, not the wrong type.

function setVolume(level) {
  if (level < 0 || level > 100) {
    throw new TypeError("level must be between 0 and 100");
  }
}

Fix: Use RangeError for numeric limits.

function setVolume(level) {
  if (level < 0 || level > 100) {
    throw new RangeError("level must be between 0 and 100");
  }
}

The corrected version communicates the actual kind of failure.

Mistake 2: Assuming Error means a built-in browser problem

Error is not a special browser-only object. It is the base error class you can use yourself anywhere JavaScript runs.

Problem: This code waits for a special runtime failure that never happens, because Error is just a class you can instantiate directly.

try {
  const problem = new Error("Oops");
  console.log(problem);
} catch (error) {
  console.log("This catch will not run unless you throw");
}

Fix: Throw the error when you want control flow to stop.

try {
  throw new Error("Oops");
} catch (error) {
  console.log("Handled:", error.message);
}

This works because only throw turns the error object into an exception.

Mistake 3: Writing a message that does not explain the rule

A useful error message tells the caller what was expected and what went wrong. Vague messages make debugging slower.

Problem: The message here does not say what value is invalid or how to fix it.

function parseRating(rating) {
  if (typeof rating !== "number") {
    throw new TypeError("Invalid value");
  }
}

Fix: Include the expected type and a clear rule.

function parseRating(rating) {
  if (typeof rating !== "number") {
    throw new TypeError("rating must be a number between 0 and 5");
  }
}

The better message makes the failure obvious to anyone who sees it in logs or tests.

7. Best Practices

Practice 1: Choose the most specific built-in error

Specific errors make intent clear and improve debugging. They also help test suites assert the right behavior.

function getPage(pageNumber) {
  if (!Number.isInteger(pageNumber)) {
    throw new TypeError("pageNumber must be an integer");
  }

  if (pageNumber < 1) {
    throw new RangeError("pageNumber must be at least 1");
  }
}

Here the type check and the range check are separated, so each failure tells the truth.

Practice 2: Put the useful detail in the message

A good message explains the rule and, when possible, includes the value that failed.

function setPort(port) {
  if (!Number.isInteger(port) || port < 1 || port > 65535) {
    throw new RangeError(`port must be an integer between 1 and 65535; received ${port}`);
  }
}

This message helps during testing because it shows both the allowed range and the bad value.

Practice 3: Re-throw errors you cannot handle

If your code cannot recover, do not silently swallow the error. Either handle it meaningfully or pass it upward.

function parseUserAge(value) {
  try {
    const age = Number(value);

    if (Number.isNaN(age)) {
      throw new TypeError("age must be numeric");
    }

    if (age < 0) {
      throw new RangeError("age cannot be negative");
    }

    return age;
  } catch (error) {
    throw error;
  }
}

The example shows the structure, but in real code you should only catch when you need to add context, clean up, or recover.

8. Limitations and Edge Cases

A common search phrase is “TypeError: Cannot read properties of undefined”. That message usually means code tried to access a property on a missing value, not that the variable name itself is wrong.

9. Practical Mini Project

In this small example, we will build a validator that checks a user profile object and throws the right built-in error type for each failure.

function validateProfile(profile) {
  if (typeof profile !== "object" || profile === null) {
    throw new TypeError("profile must be an object");
  }

  if (typeof profile.name !== "string") {
    throw new TypeError("profile.name must be a string");
  }

  if (!Number.isInteger(profile.age)) {
    throw new TypeError("profile.age must be an integer");
  }

  if (profile.age < 0 || profile.age > 130) {
    throw new RangeError("profile.age must be between 0 and 130");
  }

  return true;
}

try {
  validateProfile({ name: "Ava", age: 34 });
  console.log("Profile is valid");
} catch (error) {
  console.error(error.name, error.message);
}

This mini project shows how the three error types work together: TypeError covers the wrong shape or kind of value, and RangeError covers values that are valid in type but not valid in bounds.

10. Key Points

11. Practice Exercise

Build a function called clampRating that validates a rating value.

Expected output: a valid number between 1 and 5 returns unchanged, while invalid input throws the appropriate built-in error.

Hint: check the type first, then check the allowed range.

function clampRating(rating) {
  if (typeof rating !== "number" || Number.isNaN(rating)) {
    throw new TypeError("rating must be a number");
  }

  if (rating < 1 || rating > 5) {
    throw new RangeError("rating must be between 1 and 5");
  }

  return rating;
}

// Example usage
console.log(clampRating(4)); // 4

12. Final Summary

JavaScript error types give you a standard way to describe failures. Error is the general base class, TypeError is for wrong or unusable values, and RangeError is for values outside the allowed limits.

Choosing the right error type makes your code easier to debug and your APIs easier to use. It also helps you write better validation because the error itself tells the caller what kind of mistake happened.

As you continue learning error handling, practice throwing and catching these errors in small validation functions. That habit will make your runtime checks clearer and your debugging faster.