Skip to main content

Command Palette

Search for a command to run...

Error Handling in JavaScript: Try, Catch, Finally

Updated
5 min read

Every developer, no matter how experienced, writes code that breaks. The question isn't if your code will encounter an error — it's what happens when it does. That's where error handling comes in.

In this guide, you'll learn how JavaScript lets you anticipate errors, respond to them, and keep your program running smoothly instead of crashing entirely.

What are errors in JavaScript?

An error is JavaScript's way of saying "something went wrong and I don't know how to continue." When an unhandled error occurs, your entire script stops running — like a power cut mid-sentence.

Here are the most common types you'll encounter as a beginner:

  • ReferenceError: Using a variable that doesn't exist yet.

  • TypeError: Using a value in the wrong way (e.g., calling a non-function).

  • SyntaxError: Writing code JavaScript can't parse or understand.

  • RangeError: A number is outside an allowed range.

// ReferenceError — 'price' was never declared
console.log(price);

// TypeError — null has no property 'toUpperCase'
let name = null;
name.toUpperCase();

// Both cause the script to STOP immediately!

⚠ heads up

These are called runtime errors — they only appear when the code actually runs, not when you write it. That's why testing matters!

Using try and catch blocks

The try...catch statement lets you attempt something risky and handle the problem if it fails — without crashing your whole program.

Think of it like this: try is you attempting to open a door, and catch is the plan you have if the door is locked.

try {
  // Code that might fail goes here
  let result = riskyOperation();
  console.log(result);

} catch (error) {
  // This runs only if something went wrong above
  console.log("Oops! Something went wrong:", error.message);
}

A real-world example

Imagine you're trying to parse data that a user typed in. If they typed something invalid, JSON.parse() will throw an error. Here's how to handle it gracefully:

function parseUserInput(input) {
  try {
    let data = JSON.parse(input);
    console.log("Parsed successfully!", data);

  } catch (error) {
    // Instead of crashing, show a friendly message
    console.log("That doesn't look like valid data. Try again!");
  }
}

parseUserInput('{"name": "Alice"}'); // ✅ Works fine
parseUserInput("not valid json!!");  // ✅ Caught gracefully

💡 tip

The error object inside catch has two handy properties: error.message (what went wrong) and error.name (the type of error). Use them in your logs!

The finally block

The finally block is your guarantee. Code inside it always runs — whether your try succeeded or your catch fired. It's perfect for cleanup tasks.

Common uses for finally: closing a loading spinner, releasing a file, or hiding a progress bar.

function fetchData() {
  showLoadingSpinner(); // Show spinner before we start

  try {
    let data = getDataFromServer();
    displayData(data);

  } catch (error) {
    showErrorMessage("Could not load data.");

  } finally {
    // This ALWAYS runs — success or failure!
    hideLoadingSpinner();
  }
}

ℹ info

Think of finally as the responsible adult who tidies up no matter what happened at the party — whether it was great or a disaster.

in simple words

try  — "Let me attempt this"

You write the code you want to run. JavaScript will try its best to execute it. Like placing a food order — you tell the waiter what you want.

catch  — "If something breaks, do this instead"

If the try code fails, catch takes over. Like the waiter saying "Sorry, that dish is unavailable — here's what we can offer instead." Your app stays alive.

finally  — "Always do this, no matter what"

Runs whether the order succeeded or failed. Like the restaurant always bringing your bill at the end — regardless of how the meal went.

Throwing custom errors

Sometimes JavaScript doesn't throw an error, but you want to because the data doesn't make sense. You can create and throw your own errors using the throw keyword.

function setAge(age) {
  if (age < 0 || age > 150) {
    // Throw our own custom error
    throw new Error("Age must be between 0 and 150!");
  }
  console.log("Age set to:", age);
}

try {
  setAge(-5);    // This triggers our custom throw
} catch (error) {
  console.log(error.message); // "Age must be between 0 and 150!"
}

You can also create custom error classes for more advanced scenarios — useful when building larger apps:

class ValidationError extends Error {
  constructor(message) {
    super(message);
    this.name = "ValidationError";
  }
}

try {
  throw new ValidationError("Email is not valid!");
} catch (error) {
  console.log(error.name);    // "ValidationError"
  console.log(error.message); // "Email is not valid!"
}

Why error handling matters

Skipping error handling is like building a car without airbags — fine until something goes wrong. Here's what good error handling does for you:

graceful failure: Instead of a blank screen or frozen page, users see a helpful message. Your app stays alive.

easier debugging: Log the error.message and error.stack to understand exactly what went wrong and where.

user trust: Apps that handle errors well feel professional and reliable — even when things go sideways.

recovery: You can retry failed operations, show fallback data, or redirect users instead of just giving up.

Quick recap

  • try — wrap risky code that might fail

  • catch — handle the error when it happens

  • finally — always runs, great for cleanup

  • throw — create your own meaningful errors

  • Good error handling = better UX, easier debugging, more trust