DevAcademy
LearnJavaScriptError Handling
IntermediateJavaScript

Error Handling

Learn how to handle runtime errors gracefully in JavaScript using try/catch/finally, the Error object, custom error classes, and catching specific error types.

Reading Time

16 min

Lesson

Lesson 29 of 48

Why Handle Errors?

No matter how carefully you write code, things go wrong at runtime — a network request fails, a user types a number where an object was expected, a file doesn't exist. If you don't handle these situations, a single unexpected error can crash your entire program. Error handling lets you anticipate failure, respond to it gracefully, and keep the rest of your application running instead of dying at the first sign of trouble.

try, catch, and finally

The try...catch statement is the core tool for handling errors. Code that might fail goes inside the try block. If an error is thrown anywhere in that block, execution immediately jumps to the catch block, which receives the error as an argument. An optional finally block runs afterward no matter what happened — whether the try block succeeded, failed, or even returned early.

Basic try/catch/finally

Try it yourself — edit and run

Console Output

Click “Run” to see the console output here.

The Error Object

When you create or catch an error, you're working with an Error object. It has a message property describing what went wrong, a name property identifying the type of error (like "TypeError" or "Error"), and a stack property containing a trace of where the error occurred — invaluable when debugging. You can create one yourself with new Error("some message") and throw it whenever your code detects a problem it can't recover from.

Inspecting an Error

Try it yourself — edit and run

Console Output

Click “Run” to see the console output here.

Creating a Custom Error Subclass

Sometimes a generic Error isn't descriptive enough — you want to distinguish a validation problem from a network problem from a permissions problem. You can do this by extending the built-in Error class. Call super(message) inside the constructor to set up the message and stack correctly, then set this.name to something meaningful so catch blocks (and log output) can tell your custom errors apart from ordinary ones.

Custom Error Class

Try it yourself — edit and run

Console Output

Click “Run” to see the console output here.

Catching Specific Error Types

A single catch block often needs to handle more than one kind of failure differently — maybe a validation error should be shown to the user, while a network error should trigger a retry. Since JavaScript doesn't support multiple typed catch clauses like some other languages, the common pattern is to check the error's type inside a single catch block using instanceof, and branch accordingly.

Branching on Error Type

Try it yourself — edit and run

Console Output

Click “Run” to see the console output here.

Common Built-in Error Types

Error TypeThrown When
TypeErrorA value is not of the expected type (e.g. calling a non-function)
RangeErrorA number is outside an allowed range (e.g. invalid array length)
ReferenceErrorA variable that does not exist is referenced
SyntaxErrorCode is malformed, often from JSON.parse() on invalid JSON
ErrorThe generic base type, and the base class for custom errors

Why finally Always Runs

The finally block is guaranteed to run after the try/catch, regardless of what happens inside them — even if the try block returns a value, or the catch block throws a new error. This makes finally the right place for cleanup work that must always happen, such as closing a connection, hiding a loading spinner, or releasing a lock, no matter whether the operation succeeded or failed.

finally Runs Even After a Return

Try it yourself — edit and run

Console Output

Click “Run” to see the console output here.

Error Handling and Async Code

try/catch works well for code that runs immediately, but most real-world failures — a fetch request, a database query, a timer — happen asynchronously. A plain try/catch wrapped around asynchronous code will NOT catch errors thrown later, inside a callback, because by the time that callback runs, the try block has already finished executing. This gap is exactly what Promises exist to solve, giving asynchronous operations a proper way to report success or failure that your error-handling code can hook into — which is where the next lesson picks up.

try/catch Cannot Catch Async Callback Errors

Try it yourself — edit and run

Console Output

Click “Run” to see the console output here.

Keep catch Blocks Specific

Avoid catching errors just to silently swallow them with an empty catch block — that hides bugs instead of fixing them. Log the error, handle the specific cases you know how to recover from, and consider re-throwing errors you don't know how to handle so they surface higher up in your program where more context is available.

Only try/catch Synchronous Code (For Now)

A try/catch block only catches errors thrown synchronously within it. Errors thrown inside setTimeout callbacks, event listeners, or (as you'll see next) unhandled Promise rejections will slip right past it. Once you learn Promises and async/await, you'll see how await lets you bring asynchronous errors back into a familiar try/catch shape.

Interview Questions

Quick Quiz

1. Which block always executes, regardless of whether an error was thrown or caught?

2. What must you call inside a custom Error subclass constructor to properly set up the message?

3. Which operator lets you check whether a caught error is an instance of a specific custom error class?

4. What does the stack property of an Error object contain?

5. Why can a synchronous try/catch not catch an error thrown inside a setTimeout callback?