ArticlesNode.js Runtime and the V8 Engine
Articles

Node.js Runtime and the V8 Engine

September 2026 · 13 min read

Node.js Runtime and the V8 Engine

Nowadays, there are various types of ecosystems available, whether for Python, Java, or Node.js.

If we specifically talk about Node.js, there are many large platforms and software systems using Node.js, such as JioCinema, Uber, etc. And we are going to hear about Node.js many times if we are mainly going to be involved in JavaScript-based backend systems/ecosystems or backend development in general.

Understanding Node.js is relevant to understanding most modern JavaScript stuff. For example, if we take a modern library like React, we need Node.js on our system for React to work in development.

  • What is Node.js
  • What it is not
  • Internals of Node.js
  • How Node.js code is written
  • Chrome V8 engine
  • Internals of V8 engine

What Is Node.js

Nowadays it is pretty common knowledge that it is a runtime environment.

If you visit the Node.js website, you will find a simple definition: “Node.js is a free, open-source, cross-platform JavaScript runtime environment that lets developers create servers, web apps, command-line tools, and scripts.”

But let's take a deeper dive and understand the runtime environment part.

What Is a Runtime Environment

You might have seen newborn babies. What happens with newborn babies is that their parents give them the most suitable environment and provide them with good-quality food, shelter, etc. The parents ensure that they give the baby a habitable environment so that the baby can grow and start living. That is what a habitable environment is in human terms.

Now let's understand the habitable environment with respect to technology. For you to do something specific in a particular technology, maybe the technology is not sufficient enough, and you want to add more capabilities to the said technology. That is what a runtime environment is. A runtime environment is software. It's nothing crazy. It can provide extra ecosystem stuff that the technology inherently lacks and enhance the capability of the corresponding technology. We are talking about JavaScript as the technology here.

JavaScript is a programming language. It is modern, high-level, and has some crazy features, but the language specification itself does not define everything required to build an end-to-end application. For example, it does not define APIs for accessing files, creating servers, manipulating the DOM, or using timers. These capabilities are provided by the runtime environment in which JavaScript is running.

What extra capabilities? You might have seen functions like setTimeout and setInterval, their use cases are to to provide runtime-provided timer functionality to schedule callbacks. JavaScript in itself doesn't know how to access timers. They are not an inherent part of JavaScript. There is no such logic implemented in JavaScript itself. You can check the ECMAScript spec. So how do they work? These are some extra capabilities provided by the runtime environment. And these capabilities can vary according to the runtimes. A runtime knows a lot of interesting things, like accessing timers. One of the simplest runtime environments is your browser. A browser can act as a runtime environment for JavaScript. It provides capabilities like reading and modifying HTML, accessing timers, and using DOM-related functions. JavaScript is about loops, functions, etc.

Browsers provide networking capabilities as well.

In a nutshell, we have a runtime environment, which is software, and inside it, we have our plain JavaScript running. This runtime provides extra capabilities to JavaScript. For a major period, the browser was the main and most commonly used runtime environment for JavaScript, although some non-browser JavaScript implementations also existed.

In 2009, Ryan Dahl created Node.js with the idea of running JavaScript outside the browser and giving it access to server-side and OS-related capabilities. Node.js used V8 to execute JavaScript and provided capabilities like reading and writing files on disk, getting information about the current process, creating child processes, using timers, and making network calls with the help of OS APIs.

The networking capabilities were enhanced. In browsers, the networking capability was such that you could make a request to the outside world. Dahl's version was not only able to make external requests, but it also made it possible for other machines to communicate with your machine.

This new environment was Node.js. This made it possible to use JavaScript outside the frontend world and start building technology using JavaScript outside the browser. It also made it possible to access machine resources, due to which server-side development work is being done in JavaScript because of Node.js.

Cross-Platform Execution

In computer science, a platform includes things like the operating system, CPU architecture, system libraries, and other details of the environment in which a program runs. Windows is a different platform from macOS.

C++ source code can be cross-platform, but a compiled C++ executable is generally platform-specific. If we compile a C++ program for Windows, that same executable normally cannot run directly on macOS. We have to compile the source again for the target platform.

Technologies like Java, Python, JavaScript, and Node.js make it easier to run the same source code on different platforms. However, the corresponding JVM, interpreter, runtime, or executable still has to be built for each platform. How these technologies achieve cross-platform compatibility differs from one another.

same app.js
┌──────────────────┼──────────────────┐
▼ ▼ ▼
Node.js for Windows Node.js for macOS Node.js for Linux
│ │ │
▼ ▼ ▼
Windows + target CPU macOS + target CPU Linux + target CPU

Internals of Node

Node.js, V8, and libuv

Node provides a lot of different capabilities, including some libraries. It gives access to something called V8 and the libuv library. It gives access to the event loop and a lot of OS-level stuff.

But how is access to these capabilities provided?

The Node ecosystem internally has all of these capabilities, but the code we write gets access to many interesting functions like setTimeout. What Node does is expose this set of functions to the JavaScript layer. This JavaScript layer sometimes has the whole implementation in itself and sometimes interacts with the C++ layer. The Node.js core contains both JavaScript and native code. Some functionality is implemented in JavaScript, while other functionality is implemented using C++ bindings and native libraries.

Your JavaScript
Node.js APIs ─────► native bindings ─────► operating system
│ │
├────► V8 └────► libuv
│ executes event loop, threads,
│ JavaScript files, sockets, timers
Application result

Timer Behavior

setTimeout(callback, 0)

In Node, if you pass 0 ms as a setTimeout value, it is automatically converted to 1 ms. If the time in ms is undefined, it is automatically converted to 1 ms. If it is not undefined, it is converted to a number. If your timeout value is not in the range from 1 to the maximum value, it is converted to 1. For example, if you pass 0, it is converted to 1.

This behavior is runtime-specific.

Why Node.js Uses C++

But why use C++ along with JavaScript in the runtime? C++ offers higher and predictable performance than JavaScript especially for CPU intensive tasks and low-level operations, whereas JavaScript is not. Apart from that, it is easy to interact with many OS capabilities through C++, as it is very fast and low-level. That is why the JavaScript layer needs to interact with the C++ layer.

If you want to interact directly with the OS using JavaScript, then the OS has to expose those capabilities to the JavaScript layer.

The most important question is: How is Node.js cross-platform when it contains native C and C++ code, and compiled native binaries are platform-specific?

No single component makes Node.js cross-platform. V8 supports multiple CPU architectures, libuv handles many differences between operating systems, and Node.js contains platform-specific code where it is required. A separate Node.js binary is built for each supported platform.

Browser and Server Communication

If Node and a browser are both runtimes, how do they communicate with each other?

They communicate similarly to how two processes communicate with each other.

  • Inter-process communication: we do not use it, as the processes can be on different machines because we deploy the server and client differently.
  • Networking communication
Browser process Node.js process
┌──────────────────────┐ ┌──────────────────────┐
│ Browser APIs and UI │ │ Node.js server APIs │
│ JavaScript engine │ ── HTTP request ─► │ V8 and libuv │
│ fetch() │ ◄─ HTTP response ─ │ http server │
└──────────────────────┘ └──────────────────────┘
client server

Internals of V8

V8 is the JavaScript engine. What is a JavaScript engine? In a nutshell, a JavaScript engine has all the logic written to take JavaScript code and run it on your machine. This logic is technically written in a JavaScript engine. V8 is one of the engines alongside Chakra (legacy), SpiderMonkey, and JavaScriptCore.

Why Multiple Engines Exist

Different JavaScript engines are developed by different browser and runtime projects, and they optimize for different things. These things can include startup time, peak performance, memory usage, security, supported CPU architectures, and integration with the browser or runtime.

The target device also matters. A browser running on a high-end machine has different requirements from a JavaScript engine running on a low-powered IoT device. Some engines are therefore designed to be smaller and lighter, while engines such as V8 are designed for more complex and performance-intensive use cases.

The V8 engine is developed by Google, powers the Chrome browser, and is primarily written in C++. Interestingly, it is a different component altogether and is not tightly coupled with the browser or the Node runtime. V8 does not exist because Node exists.

Main V8 Components

There are a lot of components in the V8 engine, and together, they make it possible to run the code on the machine.

Component V8 implementation
Parser Scanner and parser
Interpreter Ignition
Baseline compiler Sparkplug
Mid-tier optimizing compiler Maglev
Peak optimizing compiler TurboFan
Earlier optimizing compiler Crankshaft

Parser

Parsing is a very common step in programming languages. In V8, the process starts with a scanner. The scanner reads the source code and converts it into tokens. The parser then takes those tokens and creates something called an AST, or abstract syntax tree.

JavaScript source
Scanner ─────► tokens ─────► Parser
Abstract Syntax Tree

It takes let x = 1;.

It takes the individual tokens and creates a tree out of them. This tree is a structured representation of the meaning and syntax of our code, but it is not an exact copy of the original source text.

But why? It makes the code understandable to the remaining components of the V8 engine and makes it compatible with later analysis and optimization.

Ignition and Bytecode

Ignition contains V8's bytecode compiler and interpreter. The bytecode compiler converts the AST into bytecode, and the Ignition interpreter executes that bytecode.

What is bytecode? It is an intermediate representation. It is not the JavaScript source code, and it is not final native machine code. It is compact and architecture-independent inside V8. However, bytecode alone does not make V8 cross-platform. V8 still needs platform-specific code to execute the bytecode and generate machine code for different CPU architectures.

Ignition was not present in the early versions of Node.

Earlier versions of V8 did not have Ignition. Before the Ignition and TurboFan pipeline, V8 used Full-codegen as its baseline compiler and Crankshaft as its optimizing compiler.

Full-codegen quickly converted JavaScript into machine code. If a function became hot, Crankshaft could compile a more optimized version of that function. Crankshaft sometimes needed to compile the function again from the source code.

Ignition later introduced a bytecode-based execution model. The bytecode could be executed by the Ignition interpreter and could also be used as input by the optimizing compiler.

Tiered Compilation and Deoptimization

That's where V8's tiered execution pipeline comes in. V8 collects some information at runtime. For example, there might be some function that is being called frequently. Ignition creates a short bytecode sequence (an unoptimized version), and once it is run, V8 collects runtime information and optimizes the code.

How? V8 collects feedback at places such as function calls, property accesses, and arithmetic operations. This feedback can contain information about the types of values and the shapes of objects that V8 has seen. V8 calls these object shapes maps.

If we repeatedly pass similarly shaped objects to a function, V8 can use that feedback when optimizing the function. An optimizing compiler can then generate machine code specialized for the patterns that were observed during earlier executions.

It is not simply caching an entire line of code or creating a copied version of the object. It is recording feedback and generating specialized machine code from that feedback.

The optimization is based on information collected during previous executions of the function. V8 can consider things like how frequently the function runs and what types or object shapes it has observed.

Depending on the code and the available feedback, V8 may move the function through different execution tiers. Sparkplug can quickly generate baseline machine code, Maglev can apply mid-tier optimizations, and TurboFan can produce more heavily optimized machine code for hot code.

JavaScript source
AST
Ignition bytecode ─────► Ignition interpreter
├───────────────► Sparkplug baseline machine code
├───────────────► Maglev optimized machine code
└───────────────► TurboFan optimized machine code
assumption fails
deoptimization
function f({name= ""}) {}

For this function, V8 can collect information about the shapes of the objects being passed to it. If it repeatedly sees the same object shape, an optimizing compiler may generate machine code specialized for that shape.

However, a call site does not always have only one shape or type representation. It can also observe several different shapes.

If a new object breaks an assumption used by the optimized machine code, V8 may deoptimize the function. It can then resume execution in a less optimized tier, collect more feedback, and possibly optimize the function again later.

repeated object shapes
runtime feedback ─────► specialized machine code
new shape breaks a guard
deoptimize
collect feedback and try again

Why can JavaScript be harder to optimize than C++?

In C++, variables commonly have types that are known during compilation. In JavaScript, variables do not normally have fixed declared types. Instead, the values stored inside them have types that are determined at runtime.

Because of this, V8 has to observe those values, perform runtime checks, and sometimes make assumptions about the types it is likely to see in the future. These checks and possible deoptimizations can introduce a performance cost.

V8 performs some compilation while the program is running. This is called just-in-time compilation, or JIT.

JIT vs AOT

Strategy Full name
JIT Just-in-time compilation
AOT Ahead-of-time compilation

In a normal C++ workflow, we compile the program into machine code before running it. This is AOT compilation.

With JIT compilation, machine code is generated while the program is running. This allows the compiler to use information collected from the actual execution of the program.

Parsing, interpreting bytecode, and garbage collection also happen at runtime, but those activities are not all called JIT. JIT specifically refers to compilation performed during execution.

So TurboFan optimizes the hot part of the code.

Apart from the capabilities of the compiler, parser, etc., it has more things, such as the memory aspect and access to the call stack and the heap.

Garbage Collection

Orinoco is the codename for V8's garbage-collection project. It is not one single garbage collector. It includes several garbage-collection techniques designed to manage memory while reducing the amount of time for which JavaScript execution has to pause.

At a high level, V8 uses a generational heap. It contains a young generation and an old generation, along with some additional specialized memory spaces.

new object
young generation
(minor collection)
│ │
unreachable│ │survives collections
▼ ▼
reclaimed old generation
(major collection)
unreachable│
reclaimed

Newly created heap objects normally begin in the young generation. Many objects become unreachable shortly after they are created, so the young generation is collected frequently.

If an object remains reachable through multiple garbage-collection cycles, it may be promoted to the old generation. This happens because the object survived previous collections, not because V8 knows that it will definitely be used again soon.

V8 uses different strategies for collecting the young generation and the complete heap. These strategies include marking, scavenging, sweeping, evacuation, and compaction.

Some garbage-collection work can happen concurrently with JavaScript execution or in parallel on worker threads. However, some phases still have to pause JavaScript execution.

Sweeping reclaims memory occupied by unreachable objects. Compaction moves surviving objects closer together so that fragmented regions can be reclaimed and larger continuous areas of memory become available.

TypeScript and V8

V8 parses and executes the JavaScript produced from the TS code. Parsing, interpreting, compilation, optimization, deoptimization, and garbage collection are all parts of the complete execution process. However, only the compilation performed while the program is running is specifically called JIT.

Revision Checklist

  1. Can you explain the difference between JavaScript, V8, and Node.js?
  2. Can you describe the roles of V8, libuv, native bindings, and operating-system APIs?
  3. Can you trace JavaScript from source code through tokens, an AST, and Ignition bytecode?
  4. Can you compare Ignition, Sparkplug, Maglev, and TurboFan?
  5. Can you explain runtime feedback, optimization, and deoptimization?
  6. Can you distinguish JIT compilation from AOT compilation?
  7. Can you explain how V8's generational garbage collection reclaims memory?