Skip to main content

What happens when an error occurs

When an error occurs in code, the engine creates an Error object, throws it and walks up the call stack looking for the nearest try...catch. If no handler is found, script execution stops; if one is found, the program continues from the line after the catch block.

Theory

TL;DR

  • The engine (for example V8 in Chrome and Node.js) hits a situation it cannot handle.
  • An error object with name, message and stack is created automatically.
  • The error is thrown and travels up the call stack.
  • The engine looks for the nearest try...catch; if the current function has none, the search continues in the calling function.
  • Without a handler the error bubbles all the way to the top and execution stops.
  • With a caught error the program continues after the catch block, and finally runs in any case.

Quick example

javascript
function test() { console.log(a); // ReferenceError } try { test(); } catch (err) { console.log("Error caught:", err.message); }

Here the error will not crash the program, because catch intercepted it.

Step 1: the engine detects the problem

While running the code the JavaScript engine hits a situation it cannot handle, for example:

javascript
console.log(a);

The variable a is not defined. The engine sees that and creates an error object:

javascript
ReferenceError: a is not defined

Step 2: an Error object is created

JavaScript automatically creates an object with information about the error:

javascript
{ name: "ReferenceError", message: "a is not defined", stack: "Stack trace..." }

It contains:

  • name, the error type;
  • message, a text description;
  • stack, the call stack, that is, where exactly the error happened.

Step 3: the error is thrown and looks for a handler

Once created, the engine throws this error up the call stack, meaning JavaScript looks for the nearest try...catch able to intercept it.

If the error happens inside a try block, JavaScript jumps straight to the matching catch. If the current function has no try...catch, the engine moves up the call stack, to the function that called the current one, and so on, until it finds a handler.

If no handler is found, the error bubbles all the way to the top and execution of the code stops.

Step 4: execution stops if the error is not caught

If the error is not caught, the program halts:

javascript
console.log(a); console.log("This code will not run");

After the ReferenceError on the first line execution is interrupted, and the second line is never executed.

Step 5: recovery and the finally block

If the error is caught in try...catch, the program continues after the catch block:

javascript
try { JSON.parse("{ not valid json }"); } catch (error) { console.log("An error occurred:", error.message); } console.log("The code continues...");

The output:

javascript
An error occurred: Expected property name or '}' in JSON at position 2 The code continues...

If you add a finally block, it always runs, no matter whether there was an error or not:

javascript
try { throw new Error("Failure!"); } catch (e) { console.log("Caught the error"); } finally { console.log("This block runs in any case"); }

That is exactly why finally is used for cleanup: close a connection, hide a loading indicator, release a resource.

The stages at a glance

StageWhat happens
1The problem is detected
2An error object is created
3The error is thrown
4A handler (try...catch) is searched for
5Execution is interrupted if the error is not caught
6Work continues if the error was handled
7The finally block runs, if there is one

Common mistakes

  • Thinking execution resumes at the point of failure after catch. It does not: the lines in try after the failing one are skipped for good, and the program continues from the code after the whole construct.
  • Expecting try...catch to catch an asynchronous error. An exception inside setTimeout or in an unhandled promise happens on a later tick of the event loop, when the outer try has already finished.
  • Ignoring stack. The call stack is what shows the chain of functions that led to the failure; logging only message throws away the most useful part.
  • Putting a return inside finally. That value overrides both the result of try and the thrown error, so the failure simply disappears without a trace.
  • Assuming an uncaught error only breaks one line. It stops the entire current call stack, so nothing else in that branch of the code runs.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.