In the summer of 2026, an independent test surfaced something that undermines half the runtime comparisons on the internet: autocannon, one of the most popular load-testing tools, is itself written in Node. On a twelve-core machine it hit a ceiling around 65,000 requests per second — and every runtime faster than that ceiling reported roughly the same number.

In other words, a typical viral "Bun vs Node vs Deno" benchmark spent part of its time measuring not the runtimes, but its own load generator.

Rerun with the Rust-based oha, the picture fell apart in a more interesting way.

HTTP throughput, JSON response, one coreoha, 12-core machine, August 2026
Deno 2.9.1
133 093req/s
Bun 1.3.14
122 170req/s
autocannon ceilingthe tool, not a runtime
65 000req/s
Node 24.18
47 734req/s

Deno beat Bun. Two minor releases delivered roughly a thirty percent throughput gain, and the "Bun always wins" ranking that has been copy-pasted from article to article since 2023 simply did not survive remeasurement. Look at where the tool's own ceiling sits: any benchmark using it could not have told the top two apart even in principle.

This article isn't about which runtime is faster. It's about why "which is faster" is almost always the wrong question — and what the right one looks like.

Why the engine shapes the graph

The differences between runtimes start below their own code — at the JS engine level.

Node and Deno run on V8. Bun runs on JavaScriptCore, the same engine as Safari. That's not an implementation detail; it's the cause of nearly every pattern you see in measurements.

JSC has a cheap interpreter that starts executing code almost immediately and climbs to JIT tiers gradually. V8 invests in compilation up front: the first milliseconds cost more, but under sustained load the JIT keeps producing increasingly aggressive machine code.

That trade shows up most clearly at process start.

Cold startmedian of 15 runs, same machine · lower is better
Bun 1.3.14
11ms
Deno 2.9.1
14ms
Node 24.18
21ms

For serverless, where every request may be a fresh process, that gap is a real, reproducible advantage — not marketing. The inverse is just as real and much harder to chart: a service that runs for hours under load gives V8's JIT exactly what it needs, time and repeated hot paths, which is one reason companies with long-running services stay on V8 despite the headline numbers.

Deno 2.9 demonstrates a third effect. A well-optimized host layer around the engine matters as much as the engine itself: Rust plus hyper under V8 outran JSC in raw throughput.

The numbers that vanish in production

Now the part that matters most. An independent test on a production-shaped service — a URL shortener with routing, validation, and a database — produced this:

Production-shaped service: routing, validation, PostgresByteIota, independent, 2026
Bun 1.3.14
12 000RPS
Deno 2.9.1
12 000RPS
Node 24.18
12 000RPS

Not "Bun slightly ahead." The same. That is the whole finding, and it is why the chart above is worth the space a chart costs: three bars of identical length say something the previous chart cannot.

The mechanics are simple. When eighty percent of a request's time is spent waiting on Postgres, JavaScript execution speed moves wall-clock time by two to five percent. That's noise. You'll gain more from a single index or from removing one redundant round trip to the database.

So throughput differences between runtimes matter in exactly two situations: CPU-bound work and cold start. Everywhere else, you're not choosing speed — whatever you think you're choosing.

Where the work is CPU-bound, the engine gap reappears immediately:

JSON.stringify on a 3.3 MB objectsingle-threaded, same input · lower is better
Bun 1.3.14
5.5ms
Deno 2.9.1
6.5ms
Node 24.18
13.6ms

Heavy serialization, image processing, parsing — that is the shape of workload where a runtime choice is a performance choice.

Where the gap is largest is not where you run production

The widest measured differences are in tooling, not in serving. Installs and test runs are where Bun's numbers stop being marginal.

Cold install, 585 packagesempty cache; Node figure is npm · lower is better
Bun
5.8s
Deno
6.0s
npm
11.8s
200 tests across 20 files, native runnereach runtime's built-in test runner · lower is better
Bun
0.02s
Node
0.14s
Deno
1.04s

A fifty-fold difference in test startup is worth having on every developer's machine and on every CI run. Note that it says nothing about the runtime you deploy — which is exactly the point, and exactly the split most comparison articles fail to make.

The undertold story: Node caught up

Half the case for Deno and Bun used to be "batteries included." Over 2025–2026, that case quietly evaporated.

Node runs TypeScript out of the box — stable since 22.18, default in 24. It ships a built-in test runner with describe/it, mocking, coverage, and parallel execution. It has --watch instead of nodemon and reads .env without dotenv. It has a permission model — not as strict as Deno's, but an order of magnitude closer than a year ago.

It's important not to confuse the mechanism here. Node does not transpile TypeScript — it does type-stripping: it deletes the types, replacing them with whitespace, and hands V8 plain JavaScript. Symbol positions are preserved, so stack traces stay correct without source maps. But anything that requires generating code — enums, namespaces with runtime logic, parameter properties — is out of scope. And no type-checking happens at all: tsc --noEmit in CI is still mandatory.

That's a deliberate engineering boundary, not an unfinished feature. Type-checking is the most expensive part of TS compilation, and Node simply refused to pay that cost at runtime. The erasableSyntaxOnly flag in TypeScript 5.8 exists precisely to catch syntax Node can't strip. Bun and Deno transpile for real and therefore accept a wider syntax.

What you buy along with the runtime

Technical characteristics are half the decision. The other half is risk, and 2026 rearranged it.

Bun stopped being an abandonment risk. In December 2025, Anthropic acquired it — the first acquisition in the company's history. The motive is transparent: Bun powers the Claude Code CLI, so the owner has a direct operational stake in the runtime working. In August 2026, Bun 1.4 shipped, fully rewritten from Zig to Rust.

But Bun remains a stability risk. The GitHub issue about RSS growth in long-lived HTTP servers — seven hundred megabytes of growth on a container with a stable heap — has been open since 2024 and is still open. There are reports of containers growing from three hundred megabytes to one and a half gigabytes per day even at idle.

Add versioning to that: breaking changes in patch releases. A change in decorator behavior between 1.3.10 and 1.3.11 cost someone their production. Pin the exact version, not latest.

The community's practical conclusion goes roughly like this: treat Bun like PHP — a short-lived process that does its work and dies. For CI, builds, and serverless, that's ideal. For a server that lives for weeks — load-test memory before the release, not after.

Deno is the opposite situation. Technically it has the most mature security model of the three: no network or disk access without explicit permissions, and npm post-install scripts are disabled by default — the thing that would have blocked several high-profile supply-chain attacks. But in March 2026, Deno Inc. went through mass layoffs; in July it shut down Deploy Classic, cutting regions from six to two. The lawsuit against Oracle over the "JavaScript" trademark is still running, and a decision isn't expected before 2027.

And the sandbox is weaker than it looks. Permissions don't apply inside JavaScript — eval, dynamic imports, and workers run at the same privilege level. --allow-run effectively opens an exit from the sandbox. The most common failure mode in real projects: a developer hits their first permission error, reaches for -A, and the security model stops existing.

How I'd choose

Not by speed. By workload profile and by which risk you're willing to carry.

  1. Legacy codebase, or a regulated industry

    Node 24 LTS

    Foundation-backed stability, full npm compatibility, the best APM and debugging story. And Node now ships everything people used to leave for.

  2. Greenfield with heavy CI

    Bun in developmentproduction only after a memory test

    Installs and tests are several times faster, TypeScript is native, one binary replaces four tools. Pin the exact version.

  3. You run untrusted code

    Deno

    Default-deny permissions are still the strongest offer on the table. Just don't couple yourself to Deploy.

  4. Serverless with a tight cold-start budget

    Bun on Vercel, or Cloudflare Workers

    This is where the ten-millisecond difference is real, measurable and worth optimising for.

  5. Your bottleneck is Postgres under load

    Whichever you already run

    Choose by ecosystem and driver maturity instead. The built-in Bun.sql is faster on microbenchmarks but has documented correctness bugs, from malformed SQL with multiple CTEs to serializing dates via toString(). For critical data, mature pg still beats faster.

What would change my mind

A formal LTS for Bun. It would unlock a native AWS Lambda runtime — today Bun lives there only through Docker, because AWS adds runtimes only after an LTS exists.

The memory-leak issue closed, plus independent, non-vendor confirmation that the Rust rewrite actually reduced memory use. Then Bun stops being a runtime for short-lived processes only.

Deno stabilizing — financially and regionally. The project deserves it on technical merit; the open question is whether the company is alive in two years.

And keep an eye on WinterTC, the Ecma committee standardizing a common set of server-side APIs across all runtimes. If it works, choosing a runtime becomes a reversible decision instead of an architectural one. It's the most interesting thing happening in this space, and almost nobody writes about it.