How does JavaScript work? π€

Hello! I'm a dedicated software developer with a passion for coding and a belief in technology's impact on our world. My programming journey started a few years ago and has reshaped my career and mindset. I love tackling complex problems and creating efficient code. My skills cover various languages and technologies like JavaScript, Angular, ReactJS, NodeJs, and Go Lang. I stay updated on industry trends and enjoy learning new tools. Outside of coding, I cherish small routines that enhance my workday, like sipping tea, which fuels my creativity and concentration. Whether debugging or brainstorming, it helps me focus. When I'm not coding, I engage with fellow developers. I value teamwork and enjoy mentoring newcomers and sharing my knowledge to help them grow. Additionally, I explore the blend of technology and creativity through projects that incorporate art and data visualization. This keeps my perspective fresh and my passion alive. I'm always seeking new challenges, from open-source contributions to hackathons and exploring AI. Software development is more than a job for meβit's a passion that drives continuous learning and innovation.
"Last updated: September 2026"
Did you know the simple statement of JavaScript needs a lot of work done behind the seen to get it executed?
Hmmβ¦ So the browser doesnβt understand javascript directly. Then how are we going to ask the browser to do something?
Letβs start with what language the browser understands. The browser only understands 0s and 1s language i.e. Statements in Binary/Bits format.
We canβt convert our whole JavaScript into bits easily. So what should we do now? π€
JavaScript engine:- βHey donβt worry I can help you with that give me your JavaScript fileβ.
What is a JavaScript engine then?
So how does the engine turn our code into something the machine runs?
The engine reads the whole file first and builds an Abstract Syntax Tree β a tree that represents the structure of our code. That tree goes to an interpreter (in V8 it is called Ignition), which turns it into bytecode and starts executing straight away. This is why JavaScript starts running almost instantly: nothing waits for a full compile.
While that bytecode runs, the engine watches it. If a function gets called again and again with the same kinds of values, the engine marks it as hot and hands it to an optimising compiler (TurboFan). That compiler produces real machine code, specialised for the types it has been seeing.
If the assumption later turns out to be wrong β say a function that always received numbers suddenly gets a string β the engine throws the optimised version away and falls back to bytecode. This is called deoptimisation.
function add(a, b) {
return a + b;
}
// Called thousands of times with numbers β V8 compiles a fast, number-specific version
for (let i = 0; i < 100000; i++) {
add(i, i + 1);
}
add("hello", "world"); // types changed β the optimised version is discarded
This is the practical takeaway from all of it: keep the types going into a function consistent. Not because "JavaScript is slow", but because switching types forces the engine to undo work it had already done for you.
Now browser can understand this machine code and behave accordingly.
Here are some JS engine examples.
| Where JavaScript runs | Engine |
|---|---|
| Chrome, Edge, Opera, Brave | V8 |
| Firefox | SpiderMonkey |
| Safari | JavaScriptCore (Nitro) |
| Node.js | V8 |
| Deno | V8 |
| Bun | JavaScriptCore |
Edge used to have its own engine, Chakra. Microsoft rebuilt Edge on Chromium in 2020, so it runs V8 now β which is why "works in Chrome, broken in Edge" is a much rarer bug report than it used to be.
Correction:-
With the Edge 79 release, Microsoft is switching to a Blink browser engine with a V8 JavaScript engine.
Both Blink and V8 are developed under Chromiumβan open-source project with an open-source web browser of the same name. Chromiumβthe open-source browserβis used by Google for its own Chrome browser. Now, Microsoft's Chromium Edge will do the same.
So what is inside this javascript engine?
Here is a very basic view of JavaScript Engine.
Memory heap
JavaScript engine is sometimes unable to allocate memory at compile-time, so variables that allocated at runtime go into memory heap (unstructured region of memory). Data/Objects that we allocate in the heap section exist even after we exit the function which allocated the memory inside the heap.
Here we face a major problem of memory leak.
So what is a memory leak?
A memory heap has limited space. If we keep using heap space without caring about freeing up unused memory. This causes a memory leak issue when there is no more memory available inside the heap.
To fix this issue javascript engine introduced a Garbage collector.
What is a Garbage collector?
Garbage collection is a form of memory management. Itβs like a collector which attempts to release the memory occupied by objects that are no longer being used. In other words, when a variable loses all its references Garbage collection marks this memory as βunreachableβ and releases it.
Execution context stack
A stack is the data structure that follows the Last In First Out (LIFO) principle (the last item to enter the stack will be the first item to be removed from the stack).
ECS stores execution context for all the functions. Execution context is defined as an object which stores local variables, functions, and objects.
In simple words, each function is pushed on the top of the sack. JavaScript engine executes the function which is at the top of this stack.
As JavaScript engine has only one ECS, it can execute only one thing at a time which is at the top of the ECS. This is what makes JavaScript single-threaded.
You must have heard of stack overflow.
What does that mean? - ECS also has limited space. So, if we keep adding function on the top of the stack. At some point, there will not be more space to add more stack frames. At this point, we get a stack overflow error.
Consider the following example.
function heyJS() {
console.log("Hello you are awesome!!!!");
heyJS();
}
heyJS();
Well, that went into an infinite recursion and we have a stack overflow error.
So as I mentioned JavaScript is a simple threaded language, which means it has only one call stack ad therefore it can only execute one statement at a time.
Wait, we also heard about asynchronous programming in javascript. So how does that work when only one task is allowed at a time?
Here comes Web APIβs and Callback queue.
Web APIβs
Web APIs are not part of the JS engine but they are part of the JavaScript Runtime Environment which is provided by the browser. JavaScript just provides us with a mechanism to access these APIβs. As Web APIs are browser-specific, they may vary from browser to browser. There may be cases where some Web APIs may be present in one browser but not in another.
Examples:-
document.getElementById();
document.addEventListerner();
setTimeOut();
setInterval();
Example:-
console.log(βFirst!β);
setTimeout(() => {
console.log(βSecond!β);
}, 1000 );
console.log(βThird!β);
/*
OutPut:-
First
Third
Second
*/
Itβs weird, right?
βSecondβ is inside the setTimeout so that will executed after 1 second.
What exactly happens behind the scene?
After 1-second WebAPI will get notified, hey you have code that you need to execute now. WebAPI βOh itβs console.log() I need to execute that, but I canβt execute this directly. Letβs send it to Callback Queueβ *βHey, Queue here is the callback please add this on your list and execute itβ. *
Callback Queue
Callback Queue or Message Queue is a queue data structure that follows the First In First Out principle (item to be inserted first in the queue will be removed from the queue first). It stores all the messages which are moved from the event table to the event queue. Each message has an associated function. The callback queue maintains the order in which the message or methods were added in the queue.
Event loop
The event loop continuously checks if the execution context stack is empty and if there are any messages in the event queue. It will move the method from the callback queue to ECS only when the execution context stack is empty.
Callback Queue
βHey, Event loop please check if ECS is empty. I have some callbacks that you need to push into ECSβ.
Event Loop
βQueue please give me callbacks ECS is empty now, I will push them on the stack to execute them.β
And finally, in the end, we will get out output.
// First
// Third
// Second
There are two queues, not one
Here is where the picture we have drawn so far is incomplete. The engine does not have one queue of pending callbacks. It has two, and they do not get equal treatment.
The task queue (you will also see it called the macrotask or callback queue) holds callbacks from setTimeout, setInterval, I/O, and UI events like clicks.
The microtask queue holds callbacks from promises β .then(), .catch(), .finally(), anything after an await β plus queueMicrotask() and MutationObserver.
The rule that decides everything:
When the call stack empties, the event loop drains the entire microtask queue. Only then does it take one callback from the task queue. Then it drains microtasks again.
Microtasks jump the line. Every time.
βββββββββββββββ
β Call Stack β
ββββββββ¬βββββββ
β empty?
βΌ
βββββββββββββββββββββ
β Microtask queue β β drained COMPLETELY
β .then await β
βββββββββββ¬ββββββββββ
β empty?
βΌ
βββββββββββββββββββββ
β Task queue β β ONE callback taken
β setTimeout click β
βββββββββββββββββββββ
β
βββββ back to the top
Let's watch it happen:
console.log("1 β script start");
setTimeout(() => console.log("2 β setTimeout"), 0);
Promise.resolve().then(() => console.log("3 β promise then"));
queueMicrotask(() => console.log("4 β queueMicrotask"));
console.log("5 β script end");
Output:
1 β script start
5 β script end
3 β promise then
4 β queueMicrotask
2 β setTimeout
Walk through it:
"1 β script start"runs immediately β it is on the call stack.setTimeouthands its callback to the Web API. After 0ms it lands in the task queue.The promise is already resolved, so its callback goes straight to the microtask queue.
queueMicrotaskputs its callback in the microtask queue, behind the promise one."5 β script end"runs. Now the script is finished and the call stack is empty.The event loop drains the microtask queue first:
3, then4.Only now does it take from the task queue:
2. ThatsetTimeout(fn, 0)waited for two promise callbacks that were queued after it. The0never meant "immediately" β it means "after the current work, and after every microtask".
Microtasks can starve the page
Because the loop drains the entire microtask queue before doing anything else, a microtask that queues another microtask never lets go:
function loop() {
Promise.resolve().then(loop); // do not run this
}
loop();
This freezes the tab. The call stack never overflows β each .then() gets a fresh stack β but the event loop never reaches rendering or the task queue again. A setTimeout recursion does not do this, because each iteration goes to the back of the task queue and gives the browser a turn.
Single-threaded, so how do we do real work?
The engine has one call stack, so heavy synchronous work blocks everything β including rendering. Sorting a hundred thousand records freezes the UI, and no amount of async fixes it, because async does not create a thread.
For genuinely heavy computation there are Web Workers: a separate thread with its own engine instance and its own stack. They cannot touch the DOM, and you talk to them by passing messages, but the main thread stays responsive while they work.
We will go much deeper into all of this β promises, async/await, and how the two queues behave under real code β later in this series.
This is just an overview of how JavaScript Engine works.
JavaScript engine is way more complex than how we discuss here today.
I will try to get deeper into the JavaScript engine in some of my future Articles.
In the next article of this series, I will explain Javascript Types, values, and variables.
Hope you like it, if yes **like & share.**
Thanks for your time.
Happy codingβ¦.
β Introduction to JavaScript Types, values, and variables in JavaScript β*





