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.
- 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.
- 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:
- 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:
- 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.
- Bun
- 5.8s
- Deno
- 6.0s
- npm
- 11.8s
- 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.
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.
Bun in development
Installs and tests are several times faster, TypeScript is native, one binary replaces four tools. Pin the exact version.
Deno
Default-deny permissions are still the strongest offer on the table. Just don't couple yourself to Deploy.
Bun on Vercel, or Cloudflare Workers
This is where the ten-millisecond difference is real, measurable and worth optimising for.
Whichever you already run
Choose by ecosystem and driver maturity instead. The built-in
Bun.sqlis faster on microbenchmarks but has documented correctness bugs, from malformed SQL with multiple CTEs to serializing dates viatoString(). For critical data, maturepgstill 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.