The Javascript Engine And Runtime
What a JavaScript engine actually is, why modern engines are neither purely compiled nor purely interpreted, and what a JavaScript runtime bundles around that engine to make browser code and Node.js code both possible.
The last post called JavaScript "interpreted or just-in-time compiled" and moved on without really explaining either half. That word "engine" also did a lot of quiet work in that post, so before going further, both deserve a real answer.
A JavaScript engine is just a program that runs JavaScript code. Chrome has one, Firefox has one, Safari has one, and they are all different pieces of software solving the same problem. The one you will hear about the most is Google's V8, because it powers both Chrome and Node.js, which is exactly why the same language runs equally well in a browser tab and on a server with no browser in sight.
Every engine, no matter who built it, is built from the same two pieces:
- A call stack, where code actually executes, through something called execution contexts (its own post is coming later in this series).
- A heap, an unstructured pool of memory where every object your program creates actually lives.
That answers where code runs. It does not answer how your JavaScript text turns into something the CPU can run at all, and that question needs its own detour first.
Compilation vs Interpretation, the Actual Difference
Every program, in every language, eventually has to become machine code, since a CPU only understands zeros and ones. How it gets there splits into two approaches.
Compilation converts the entire source file into machine code up front, then writes that result into a portable file. Whatever you double-click to run on your computer right now was compiled long before this exact moment. It's running from that leftover file, not from its original source.
Interpretation skips the separate file entirely. An interpreter reads your source code and executes it line by line, converting each line to machine code right as it gets to it, instead of translating the whole thing ahead of time.
JavaScript used to be a purely interpreted language, and for a long time that was fine. It stopped being fine once web apps stopped being static pages. Picture an online store where clicking "apply coupon" took a full second to actually respond, because the checkout logic was being translated line by line, every single time, instead of already being machine code. That is exactly how slow pure interpretation gets under real load.
Just-in-Time Compilation: JavaScript's Actual Answer
Modern engines do not pick one side. They compile the whole script to machine code up front, like a compiled language, then execute that machine code immediately, with no separate portable file left behind. This is just-in-time (JIT) compilation, and it's the reason "JavaScript is interpreted" stopped being an accurate sentence years ago.
Inside V8, that compilation step is not one flat pass. Your code first gets parsed into a tree structure called the abstract syntax tree (AST), a representation of every meaningful piece of your code (keywords, declarations, expressions) organized in a structured way the engine can actually work with.
let total = 42;Even one line this small produces a real tree underneath: a variable declaration node, a let node, a name node, a value node. A full application produces an AST enormous enough that no one is meant to read it by hand, it exists purely so the next stage, generating machine code, has something structured to work from.
That AST has nothing to do with the DOM tree you have been building all through the async chapter of this series. Same word, "tree", completely unrelated data structures living in completely different places.
The Optimization Trick That Makes V8 Fast
Compiling everything perfectly before running a single line would be slow to start, so modern engines cheat in a smart way. They generate a quick, unoptimized version of the machine code first, just to start running as fast as possible, then keep recompiling a faster version in the background while your code is already executing.
Each time a better version finishes, the engine swaps it in mid-run, without ever pausing execution to do it. All of that parsing, compiling, and re-optimizing happens on separate threads inside the engine, threads your code has no access to and never sees, completely apart from the single main thread your actual JavaScript runs on.
That is the real story behind "JavaScript is fast now." Not one clever trick, but three: JIT compilation, an AST-driven pipeline, and continuous background optimization, none of which existed when people first called JavaScript "just an interpreted scripting language."
A Runtime Is Bigger Than the Engine
Everything above describes the engine. A JavaScript runtime is the bigger box the engine sits inside, and in a browser, that box contains three more things beyond the engine itself.
ExpandA JavaScript runtime as a box containing the engine (call stack and heap), Web APIs, and a callback queue, connected by the event loop.
Web APIs are functionality the browser hands to JavaScript that isn't actually part of the JavaScript language itself: the DOM, timers, fetch, even console.log. JavaScript reaches all of it through the global window object. It's borrowed capability, not something built into the language itself.
The callback queue holds callback functions that are ready to run but waiting their turn, the click handler that fires the instant the stack is free to take it.
The event loop is what actually moves those callbacks from the queue into the call stack once it empties out. This series already covered exactly how that works, in real depth, back in the async chapter, so there's no need to re-walk it here, that post is still the one to read for the full mechanic.
Node.js Swaps the Box, Not the Engine
Node.js runs the same V8 engine Chrome does, which is the whole reason "JavaScript outside the browser" works at all. What changes is everything around the engine, not the engine itself.
There's no browser handing out Web APIs, so Node.js substitutes its own: C++ bindings that expose things like file system access, and a thread pool for work that genuinely needs to run off the main thread. The shape (engine plus a support box around it) stays identical. What fills that box just depends on where the runtime lives.
The Essentials
- A JavaScript engine is just software that executes JavaScript code, and every browser has its own, V8 being the most well known since it also powers Node.js.
- Compilation translates everything up front into a portable file; interpretation translates and runs line by line, with nothing saved. JavaScript now does neither in its pure form.
- Just-in-time compilation compiles the whole script at once and executes it immediately, no portable file, no waiting for a separate build step.
- Code gets parsed into an abstract syntax tree before it becomes machine code, an internal representation with zero relation to the DOM tree despite the shared name.
- Modern engines start with fast, unoptimized code and swap in better versions while already running, on background threads your code never touches.
- A runtime is the engine plus Web APIs, a callback queue, and an event loop, in the browser. Node.js keeps the same shape but swaps Web APIs for C++ bindings and a thread pool.
The call stack got a one-line mention here, execution contexts pushed and popped in an order that has to make sense. The next post pulls that thread all the way out.
Keep reading