Start a JavaScript project today and a question that used to be fairly boring can become philosophical before you have written anything useful.

What is going to run it?

Node.js is still the historical default. But there is Deno, Bun, Cloudflare Workers and its workerd runtime, AWS LLRT, QuickJS inside all kinds of embedded projects, Hermes inside React Native, JavaScriptCore under Bun, and V8 under Node and Deno.1718 That is before getting into engines made for televisions, microcontrollers, native apps, databases, or experiments whose main purpose is to find out how strange this family tree can become.

Jamie Brandon spent more than a year mapping that family tree for an article published in July 2025.1 A month later, Theo Browne turned it into a long video walkthrough built around the same question: if all of this eventually executes JavaScript, why do we keep building new machines to do it?2

From the safe distance of a package.json, the obvious answer is ecosystem disease. JavaScript already had enough package managers, bundlers, frameworks, and highly opinionated ways to turn a button into a component. Apparently it also needed several virtual machines for console.log().

The list becomes much less ridiculous when you stop comparing logos and look at the machine the code is supposed to live on.

A server process that stays alive for three weeks has a different problem from a function that must start during a request. An iPhone app has different constraints from a Linux server. A microcontroller with memory measured in kilobytes cannot afford what a laptop treats as background noise. An edge network wants to load code close to the user without booting an entire little computer for every script.

Those are not marketing variations on one problem.

They are different problems that happen to speak the same language.

An engine is not a runtime

Part of the confusion comes from using several layers as if they were interchangeable.

V8, JavaScriptCore, SpiderMonkey, QuickJS, and Hermes are primarily JavaScript engines. Their central job is to understand the language and execute a program. Depending on their architecture, they interpret bytecode, compile parts of the program just in time, compile some of it ahead of time, manage memory, and optimize code that gets hot.

A runtime adds the world around the engine.

It decides how code reads a file, opens a socket, waits for a timer, imports a module, resolves a package, exposes environment variables, reaches the operating system, asks for permissions, or responds to an HTTP request. Node uses V8, but Node is not V8.17 Deno also uses V8,18 while historically making very different choices around permissions, modules, tooling, and Web APIs. Bun uses JavaScriptCore, Safari's engine, then builds an environment around it that deliberately aims for broad Node compatibility.78

That distinction explains why swapping an engine does not automatically replace a platform.

Two runtimes can share V8 and expose very different ways to run an application. Two others can use different engines while trying very hard to execute the same Node project unchanged.

Several competitions are happening at once. This is an excellent arrangement for online arguments where everyone is technically correct about a different object.

Node won the long-running server

When Node.js arrived in 2009,1 its important move was not simply to let JavaScript escape the browser. JavaScript engines had already been embedded elsewhere.

Node gave JavaScript a convincing general-purpose server shape.

A program starts, keeps a process alive, listens for connections, reaches the filesystem, loads native libraries, and waits for work. This model makes particular sense when startup cost is spread across a long process lifetime. The engine gets time to optimize frequently executed code. Connections can remain open. Caches can stay in memory. The environment looks enough like a normal Unix process that decades of tools know what to do with it.

Node's own introduction still describes an application as a single process built around asynchronous I/O primitives, with V8 executing the JavaScript.17

The combination became an enormous target. npm grew around it. Frameworks assumed Node APIs. Native addons assumed its interfaces. Hosting platforms added “Node.js” as an execution environment. Tooling accumulated until a runtime was no longer just an implementation choice. It was an ecosystem contract.

For a while, asking which JavaScript runtime to use on a server was nearly the same thing as asking which version of Node to install.

Then the server itself started changing shape.

Cloudflare did not need a better Node process

Edge computing makes the difference unusually easy to see.

A platform such as Cloudflare Workers wants to execute small pieces of code across many points of presence around the world. It needs to load one customer's code quickly, handle a request, then keep the same infrastructure available for somebody else's code without reserving a full operating-system process and server stack for each script.

Cloudflare built Workers around V8 isolates rather than a model where every function owns its own Node process. Its documentation describes one runtime hosting hundreds or thousands of isolates, with memory separated between them.3

The engine did not disappear.

It is still V8.

What changed was the unit of isolation, the lifecycle, and the APIs exposed around the engine.

workerd, the open-source runtime that shares code with Cloudflare Workers, explicitly describes itself as server-first and standards-based. Its interface leans on fetch(), Request, Response, streams, and other primitives familiar from the Web instead of pretending each execution is a tiny Unix box.4

That trade buys something and gives something up.

Starting a small isolate inside an already running engine carries very different overhead from starting a complete process. In return, code does not naturally get the same freedom as a traditional Node process. Filesystems, arbitrary executables, process lifetime, and assumptions about persistent state work differently or may not exist at all.

Theo spends several minutes drawing this distinction in his video. The useful point is not that V8 somehow fails at the edge. Cloudflare uses V8. The problem is that reproducing the conventional Node process model for every tiny piece of edge code would spend resources on the wrong layer.2

The runtime changes because the logical computer changed.

LLRT and QuickJS pick another trade

AWS Labs explores almost the opposite direction with LLRT, the Low Latency Runtime.

Instead of keeping a large engine around and isolating scripts inside it, LLRT tries to make the runtime itself light enough that starting it becomes much less expensive. The project is written in Rust and built on QuickJS.5

QuickJS is not trying to beat V8 at every job.

Fabrice Bellard's documentation presents it as a small, embeddable engine made of a few C files with no external dependency and a very small runtime startup footprint.6 The June 2026 release implements most of ES2025 while keeping that basic shape: small code, simple embedding, low startup cost.

This is where “which JavaScript engine is fastest?” starts becoming a nearly useless question unless the workload comes with it.

A program that stays alive and executes the same hot paths millions of times can benefit enormously from a sophisticated optimizing engine. It has time to pay for warmup and make that investment back. A tiny function that starts, performs a few operations, then disappears may care much more about the time before the first useful instruction than the maximum throughput reached ten seconds later.

LLRT is explicitly experimental. AWS Labs publishes its own benchmarks and cost comparisons, which are useful measurements of the project's goals but not universal laws.5

The project still makes the design logic unusually clear. If the central problem is cold start for a small serverless function, it can make sense to build around a tiny engine rather than inherit every trade made by a browser engine that later became the foundation of a general-purpose server runtime.

“Fast” means something else on a phone

Hermes tells the same story under a different set of constraints.

Meta announced Hermes in 2019 as a JavaScript engine built specifically around React Native on constrained mobile devices.10 When Hermes became React Native's default engine in 2022, the team described three priorities in unusually practical terms: startup time, app size, and memory consumption.11

One important Hermes choice is compiling JavaScript to bytecode ahead of time and bundling that result with the app. The phone therefore has less compilation work to perform during launch.11

That is not automatically the best trade for a dynamic server workload. It makes much more sense for a mobile application that is built before distribution and is expected to show useful UI as quickly as possible after somebody taps its icon.

In February 2026, React Native 0.84 moved the layer again by making Hermes V1 the default on both iOS and Android, with a new generation of its compiler and virtual machine.12

The interesting part is not whether Hermes wins a generic benchmark. It is that an engineering team had enough mobile-specific constraints to justify building an entire JavaScript engine around a narrower definition of performance.

The language remains ECMAScript.

The machine decides what “fast” means.

Then there are computers that barely have anything

Jamie's article gets particularly entertaining when it leaves servers and phones and drops down into microcontrollers.1

Node feels lightweight on a modern laptop. That impression does not survive contact with a device whose RAM is measured in kilobytes.

Duktape, JerryScript, Espruino, Moddable, Elk, and other small engines exist for environments where the engine's size is a direct hardware constraint. These machines may control sensors, household objects, tiny displays, or embedded boards. Their goal is not to shave another second off a Next.js build. Sometimes the achievement is simply fitting a useful interpreter and the application into the available silicon.

At that scale, JavaScript running at all can be more interesting than its place in a benchmark table.

This points to a major reason engines keep multiplying: JavaScript has an enormous population of developers and libraries, so there is practical value in bringing the language into a new environment even when doing so requires rebuilding the machine that executes it.

The platform follows the developers.

It also explains stranger branches of the family tree: JavaScript engines implemented in Rust, Java, Zig, and other languages; engines embedded in databases or graphical applications; televisions that expose JavaScript as an application layer; products that use it as a scripting language while the user never sees the runtime underneath.

Sometimes the runtime is the product. Sometimes it is a replaceable part deep inside another machine.

Ranking all of these together would be a bit like asking whether a Formula 1 engine is better than the engine in a backup generator. Both turn a shaft. That does not make a useful championship.

The strange part: Node's challengers keep copying Node

If the story ended here, we would simply have an explosion of specialized niches.

Something almost opposite has been happening above the engines.

Deno is the clearest example.

Ryan Dahl introduced Deno in 2018 alongside a sharp critique of several decisions that had accumulated in Node: node_modules, module resolution, parts of the legacy API surface, and the lack of an explicit permission model.1 Early Deno pushed URL imports, built-in TypeScript support, secure defaults, and a surface shaped heavily around Web standards.

Then reality reminded everybody of one inconvenient fact: the Node ecosystem is enormous.

Deno gradually added npm compatibility, package.json, node_modules, CommonJS, node: built-ins, and other pieces required to run existing Node projects. Its current documentation says most Node projects can run with little or no change and explicitly tracks compatibility against Node's own test suite.9

That is not necessarily Deno admitting defeat.

It is a better demonstration of what Node actually won.

Node built an implicit agreement between millions of programs. A new runtime can offer a cleaner permission model, different tooling, another deployment architecture, or a better development loop. If it wants existing applications, it still has to understand the operational dialect that Node left behind.

Bun makes the same choice even more explicitly. Its documentation presents Node compatibility as a core goal and treats incompatibilities in real Node projects as bugs worth fixing.78

This creates an unusual picture.

The engines diverge.

The runtimes compete.

Yet the runtimes spend increasing amounts of engineering effort making the same code work.

Node-API makes Node less dependent on V8

The convergence reaches all the way down into native extensions.

Historically, a Node addon written in C or C++ could depend fairly directly on V8 details. That is powerful, but it ties the addon to a particular engine and often to particular versions of that engine.

Node-API exists to break that dependency.

Node's documentation describes it as an API for native addons that is independent of the underlying JavaScript runtime and ABI-stable across Node versions.13 Native code manipulates abstract values through the API instead of attaching itself directly to V8's internal representation.

The immediate benefit was obvious: native modules become less fragile when Node updates V8.

The abstraction turned out to be broader than Node itself.

Deno supports Node-API addons today.9 Bun has implemented a large part of Node-API on top of JavaScriptCore.8 Other runtimes and frameworks can adopt the same boundary.

So an API carrying Node's name has become one of the tools that lets code depend less on Node's historical engine.

That inversion says something about where the valuable layer is moving. As libraries stabilize around shared interfaces, application code needs to know less about the exact virtual machine underneath.

The Web is becoming the other shared language

The browser provides another convergence layer.

fetch, URL, Request, Response, ReadableStream, TextEncoder, Web Crypto, and many other APIs were created or standardized for the Web and later moved into server runtimes.

That matters because it gives runtimes a common surface that is not owned by Node.

workerd explicitly leans into Web standards.4 Deno has made that part of its identity from the beginning. Node has added Web APIs over time. Bun exposes Web APIs while also chasing Node compatibility.7

None of this produces perfect portability.

A browser still does not have the same filesystem as a server. A Cloudflare Worker is not a Linux process. A Lambda does not have the lifetime of a daemon. Permissions, sockets, timers, modules, CPU limits, and storage models still create real differences.

But a growing amount of ordinary application code can speak a shared vocabulary.

At that point, the runtime competition starts looking less like several programming languages fighting for territory and more like several operating systems trying to implement the same useful calls.

WinterTC assumes there may never be one winner

The convergence became important enough to produce a standards effort of its own.

WinterCG was created to define common APIs across server-side JavaScript environments. In January 2025, that work moved into Ecma as TC55, also known as WinterTC.14

Its mandate is unusually clear: define a minimum common API for server runtimes based on existing Web standards where possible, then make conformance testable.15

That is a very different project from trying to crown a universal runtime.

It starts by accepting that Node, Deno, Workers, and other environments will continue to exist, then asks how to make that plurality cheaper for developers.

In June 2026, TC55 published ECMA TR/114, Runtime keys, a technical report defining consistent identifiers for runtimes in project configuration, package manifests, conditional exports, and runtime detection.16

It is a small, almost bureaucratic standard.

It also captures the moment perfectly: there are now enough JavaScript runtimes that a standards body has to specify how software should name them.

Rather than proving the ecosystem has collapsed under fragmentation, the work exists to help code travel between the fragments.

Library authors now need a name for the machine

The runtime boom still creates a real cost for people building tools.

A library cannot always assume process, a filesystem, arbitrary sockets, or one particular native API exists. A framework may need different adapters depending on where it runs. A package may ship a different path for Node, Deno, Bun, or an isolate environment. Build tools need to distinguish targets without every project inventing a new trick based on whichever global variable happens to exist.

That is the mundane problem Runtime keys tries to solve with canonical identifiers such as node, deno, bun, and workerd, plus a governance process for new names.16

The report is careful about what those names do not mean. Runtime identity should not replace feature detection.16 Knowing code runs on Bun does not guarantee that a particular API exists in the exact version being used. Testing for a capability remains more robust whenever that is practical.

This boring administrative layer is a sign of maturity.

Early in a platform race, everybody exposes primitives and hopes enough developers show up to make their version the convention. When several platforms survive, the less glamorous work begins: naming things consistently, documenting the overlap, standardizing what can be shared, and letting the genuinely platform-specific pieces remain different.

It does not eliminate fragmentation.

It makes fragmentation survivable.

The fragmentation is moving below the waterline

There are two ways to look at the last decade.

The first is dizzying: V8, SpiderMonkey, JavaScriptCore, QuickJS, Hermes, Node, Deno, Bun, workerd, LLRT, embedded runtimes, specialized forks, native frameworks, and a collection of projects whose names occasionally feel as if they were chosen five minutes before the first Git push.

The second picture is nearly the reverse.

A developer can now write code using fetch, install an npm package, load a native addon through Node-API, and find several very different runtimes actively trying to execute that application.

Implementation diversity is increasing at the same time as the common surface is increasing.

That may be the more important trend.

Computing has done this before. Hardware can become more varied while software absorbs more of the difference. CPU architectures change while compilers keep most application programmers away from pipeline details. Networks are spectacularly heterogeneous, while HTTP provides an abstraction stable enough to build the Web on top.

JavaScript appears to be moving in a similar direction at its own scale.

The engine becomes an infrastructure decision.

The runtime becomes a platform decision.

The application tries to stay above both.

This does not make runtime choice irrelevant

Convergence can easily mutate into another slogan: write once, run everywhere.

We have put enough technologies through that particular machine already.

The differences remain important as soon as a program needs more than the shared layer.

A service that launches ffmpeg, opens unusual sockets, depends on native threads, holds large amounts of state in memory, or relies on a very specific addon will not magically move into an edge Worker because it also uses fetch().

Likewise, code designed around an isolated ephemeral runtime can make lifecycle and security assumptions that do not match a conventional long-running Node server.

Even Bun and Deno's Node compatibility is ongoing work because Node is not a small frozen specification. It is more than fifteen years of accumulated platform behavior, native modules, tooling, edge cases, and occasional bugs that became contracts because enough software depended on them.89

Portability therefore comes in layers.

Plain JavaScript is extremely portable. Shared Web APIs often travel well. Widely used Node APIs are becoming portable across more runtimes. Every step toward a platform's special capabilities reduces how far the program can move without adaptation.

That is not automatically a flaw.

It is the price of using what makes the chosen machine useful in the first place.

Pick a lifecycle before you pick a benchmark

For an actual project, this history gives a more useful rule than another runtime benchmark leaderboard.

Before asking “which one is fastest?”, ask how long the program lives, what it is allowed to touch, and which part of its cost actually matters.

A general-purpose API server with a large Node dependency graph and long-running processes has good reasons to stay on Node. A project that wants a different toolchain while retaining Node compatibility can evaluate Deno or Bun against its real dependencies. Edge code needs to be designed around the isolation model and resource limits of its hosting platform. A small Lambda that is unusually sensitive to startup latency may make LLRT worth testing, with its experimental status kept firmly in view. A React Native application benefits from a mobile-oriented engine without the team choosing Hermes in the same way it chooses a server runtime.

“Depends” is not a very satisfying answer.

Unfortunately, computers never agreed to arrange their tradeoffs for the convenience of benchmark threads.

The useful change is that choosing a runtime is becoming less like choosing a language and more like choosing how that language should execute.

Node could win the war without everybody running Node

This may be the strangest outcome of the runtime boom.

Deno was created partly as a reaction to Node, then learned to read package.json and run npm packages. Bun uses a different engine but invests heavily in Node compatibility. Node-API lets native extensions detach themselves from V8. WinterTC is building a common baseline across multiple environments. Web APIs keep crossing the browser/server boundary.

The likely endpoint is not one runtime defeating all the others.

It is a world where Node becomes a compatibility layer, the Web becomes another compatibility layer, and the runtime increasingly becomes something chosen for the physical constraints underneath.

That does not make Node, Deno, Bun, or Workers disappear. It may make it easier for them to remain different, because the common layers reduce the cost of that difference.

Jamie ended his survey by arguing that there is no single best runtime because startup, sustained performance, size, API support, and native access pull in incompatible directions.1 Theo reaches the same basic point in his walkthrough: these environments are not optimizing the same computer.2

A year later, the most interesting development is that the ecosystem seems to have accepted that answer.

It is no longer only building candidates for a winner.

It is building the standards that let many runtimes exist without forcing every application to care which one is underneath.