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} Let 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.
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."
try {
// risky code
throw new Error("Something bad happened");
} catch (err) {
console.log("Caught it:", err.message);
}
console.log("Still running");
Caught it: Something bad happened
Still running
Cool. But this just tells what happens. Let’s talk about how it happens.
Here’s a minimal example:
function c() {
throw new Error("💥 boom");
}
function b() {
c();
}
function a() {
b();
}
a();
[ 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.
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.
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.
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
An error is thrown
No entry in the handler table
The engine has no idea what to do
Program crashes
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.
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
└────────────────────┘
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!
Join Hardik on Peerlist!
Join amazing folks like Hardik and thousands of other people in tech.
Create ProfileJoin with Hardik’s personal invite link.
9
26
0