Behind the Curtain: How Try-Catch Really Works in JavaScript

Hardikk KambojHardikk Kamboj
4 min read

You’ve probably written try-catch in JavaScript hundreds of times. Feels like magic, right? Error happens, the program doesn’t crash, and the code moves on. But why? What's going on under the hood?

In this blog, I’ll walk you through exactly how JavaScript handles errors, and why you’ve probably written {try-catch} in JavaScript hundreds of times. Feels like magic, right? Error happens, the program doesn’t crash, and the code moves on. But why? What's going on under the hood?

In this blog, I’ll walk you through exactly how JavaScript handles errors, why try-catch lets the code live another day, and see what the JavaScript engine, like V8, does behind the scenes. No fluff, just pure breakdown, analogies, bytecode, diagrams, and clarity.


1. What is try-catch?

In plain words, it's a block where you tell JavaScript:

"Yo, this might break. If it does, don’t panic. Here’s how to handle it."

Example

try {
  // risky code
  throw new Error("Something bad happened");
} catch (err) {
  console.log("Caught it:", err.message);
}
console.log("Still running");

Output:

Caught it: Something bad happened
Still running

Cool. But this just tells what happens. Let’s talk about how it happens.


2. Without try-catch, what happens when an error is thrown?

Here’s a minimal example:

function c() {
  throw new Error("💥 boom");
}
function b() {
  c();
}
function a() {
  b();
}
a();

What the call stack looks like:

[ a ]
[ b ]
[ c ]

c() throws an error. JavaScript looks for a handler (a catch block) starting from c, doesn’t find any, so it moves up the stack, to b, then a, then crashes.

No handler? Boom, crash.


3. With try-catch, the game changes

function c() {
  throw new Error("💥 boom");
}
function b() {
  c();
}
function a() {
  try {
    b();
  } catch (err) {
    console.log("Caught:", err.message);
  }
}
a();
console.log("Still alive");

Now when c() throws, the engine climbs up the stack and finds a catch in a. Jumps there. Handles error. Continue the code.

No crash.


4. Call Stack + Execution Flow Diagram


5. Wait, but how does JS know where to jump?

That’s where things get juicy.

When the engine parses your code, it doesn’t keep try and catch around in their raw form. It compiles them into bytecode, the lower-level code that the JS engine runs.

And to make error handling possible, it also builds something called a handler table.


6. What is a handler table?

Think of it as a secret map that says:

"If something fails between this instruction and that one, jump to this other place (catch block)."

It looks like this internally:

Try Start Try End Catch Location Byte 10 Byte 30 Byte 50

So when an error is thrown, the engine checks:

  • Am I between byte 10 and 30?

  • Yes? Then jump to byte 50 (that’s your catch block)

Handler Table


7. Okay, but what about the crash?

Without try catch:

  • An error is thrown

  • No entry in the handler table

  • The engine has no idea what to do

  • Program crashes

With try catch:

  • An error is thrown

  • The engine checks the handler table

  • Finds a match

  • Jumps to catch the block

  • Mark's error as handled

  • Execution resumes

Magic? Nah. Just clever compiler engineering.


8. Bytecode Visual Flow

 Source Code ─────► Bytecode
                     │
                     ▼
         ┌────────────────────┐
            Bytecode Block            
            Console.log(...)                
            Throw instruction 
         └────────┬───────────┘
                       ▼
         ┌────────────────────┐
            Handler Table      
            start, end, catch  
         └────────┬───────────┘
                       ▼
         ┌────────────────────┐
             Runtime Engine     
             consults table     
             jumps to catch     
         └────────┬───────────┘
                       ▼
         ┌────────────────────┐
            catch block runs  
            and logs error    
         └────────────────────┘

9. TLDR

  • JavaScript engines compile try-catch into bytecode

  • A hidden handler table stores which errors map to which catch blocks

  • When an error is thrown, the engine checks this table and jumps to the catch location

  • If there’s no handler, the program crashes

  • try-atch isn’t just syntax, it’s a controlled escape route built into the engine

See content credentials

Try-Catch Flow


Hope this gave you an inside-out view of try-catch block, which you might be using frequently. If it still feels like magic, that’s okay, you just peeked into the bytecode black box, and that’s more than 99% of devs ever do.


I will be diving deep into more engineering topics or concepts I find interesting, so do follow!

10
Subscribe to my newsletter

Read articles from Hardikk Kamboj directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Hardikk Kamboj
Hardikk Kamboj

Hey I talk about tech, experiences, and my learnings here!