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

Table of contents
- 1. What is try-catch?
- Example
- Output:
- 2. Without try-catch, what happens when an error is thrown?
- What the call stack looks like:
- 3. With try-catch, the game changes
- 4. Call Stack + Execution Flow Diagram
- 5. Wait, but how does JS know where to jump?
- 6. What is a handler table?
- 7. Okay, but what about the crash?
- Without try catch:
- With try catch:
- 8. Bytecode Visual Flow
- 9. TLDR

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!
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!