Skip to main content

Command Palette

Search for a command to run...

Error Handling in JavaScript: Try, Catch, Finally

Published
5 min read
Error Handling in JavaScript: Try, Catch, Finally

If you are a developer, at some point, you have seen your program crash with a cryptic red message in the console, and had absolutely no idea where things went wrong.

Errors are not the enemy. They are information. The real problem isn't that errors happen, it's when they happen silently, or crash your entire application, or give the user a broken experience with no explanation.

Good error handling is the difference between a program that collapses the moment something unexpected happens, and one that responds gracefully, informs the user, and keeps running where it can.

JavaScript provides tools like try, catch, and finally to manage errors gracefully. Instead of crashing your application, these tools allow your program to recover and continue running.

1. What Errors Are in JavaScript

JavaScript has several built-in error types, each representing a different kind of problem:

We can devide errors into two broad categories for understanding:

  1. Syntax Error : caught before code runs. Your editor or the JavaScript engine spots them immediately. You can't handle these with try/catch because the code never starts executing.

  2. Runtime Error : happen while the code runs. These are the ones error handling is designed for, like : unexpected conditions, bad data, failed network requests, null values where objects were expected.

2. Understanding try and catch Blocks

The try/catch block is JavaScript's fundamental error handling structure. The concept is simple: try to run some code, and if anything goes wrong, catch the error and handle it.

try {
  let result = undefinedVariable;
} catch (error) {
  console.log("Error caught:", error.message);
}

Runtime Error Examples

Without try/catch, a runtime error crashes the entire program, everything after it stops running.

Without Error Handling

let result = undefinedVariable;
console.log(result);

This will crash the program.

With try/catch, execution jumps to the catch block, handles the error, and continues normally afterward.

Common Error Scenarios

  • Accessing undefined variables

  • Invalid operations

3. The finally Block

finally is a block that runs no matter what, whether the try succeeded, whether an error was caught, even if there's a return statement inside try or catch.

Syntax :

try {
  // attempt something
} catch (err) {
  // handle failure
} finally {
  // ALWAYS runs
}

Primary use cases of finally : cleanup resources that need to be released, loading spinners that need to be hidden, connections that need to be closed, regardless of what happened.

Example :

async function submitForm(formData) {
  showLoadingSpinner();

  try {
    let result = await sendToServer(formData);
    showSuccessMessage("Form submitted!");
    return result;
  } catch (err) {
    showErrorMessage("Submission failed. Please try again.");
  } finally {
    hideLoadingSpinner();  // always hides — success or failure
  }
}

Without finally, you'd need to call hideLoadingSpinner() in both the try and catch blocks. Duplicating code and risking missing it in one path. finally guarantees it runs once, cleanly, regardless of the outcome.

4. Throwing Custom Errors

So far, we've been catching errors that JavaScript throws. But you can throw errors yourself, and you should, when your code encounters a condition it can't or shouldn't handle silently.

The throw keyword works with any value, but throwing an Error object is best practice because it includes a stack trace.

throw new Error("Something went wrong");
throw new TypeError("Expected a string");
throw new RangeError("Value must be between 1 and 100");

Example :

function createUser(name, age) {
  if (!name || typeof name !== "string") {
    throw new TypeError("Name must be a non-empty string");
  }

  if (age < 0 || age > 120) {
    throw new RangeError("Age must be between 0 and 120");
  }

  return { name, age, createdAt: new Date() };
}


try {
  let user = createUser("", 25);
} catch (err) {
  console.error(`\({err.name}: \){err.message}`);
  // "TypeError: Name must be a non-empty string"
}

5. Why Error Handling Matters

Error handling is a professional practice. Here's why it matters beyond just keeping your app from crashing.

  1. User Experience : An unhandled error shows the user a broken, frozen, or confusing interface. A handled error shows them a clear message and a path forward. This is the difference between a product that feels trustworthy and one that feels fragile.

  2. Debugging : Caught errors with logged stack traces tell you exactly what went wrong, where, and under what conditions. Uncaught errors that silently corrupt state are some of the hardest bugs to diagnose.

  3. Separation of Concerns : Throwing errors at the point of failure and handling them at the appropriate level keeps your code clean. A database function doesn't need to know how to show UI messages it throws. The UI layer catches and responds.

Conclusion

Error handling isn't the fancy part of JavaScript, but it's one of the most important habits you can build as a developer.

The pattern is simple: try the risky code, catch what goes wrong, finally clean up regardless.

Throw your own errors when you catch invalid conditions early. Use custom error classes when different failures need different responses.

Code that handles errors well doesn't just survive the unexpected, it communicates clearly, recovers gracefully, and earns the trust of the people using it.