Published 4 mrt. 2025

Picking The Perfect JS Runtime

Or why benchmarks are misleading

#javascript
#performance
Cover image for Picking The Perfect JS Runtime

Picking JavaScript runtimes have real consequences. We've tracked performance metrics, revealing striking differences between Node.js, Deno, and Bun. Memory consumption patterns in production tell a compelling story that synthetic benchmarks miss entirely.

The Node.js Era

Introduced in 2009 (approximately 15 years ago), Node.js is a JavaScript runtime built on Chrome’s V8 JavaScript engine. Node.js extended JavaScript beyond the browser, enabling developers to use the same language for both client and server side, popularizing full-stack JavaScript.

Since then, we’ve navigated through numerous challenges including (but not limited to):

Thankfully, the JavaScript ecosystem has evolved significantly since then.

Server Side Rendering

At brainhive, we’ve built our backend infrastructure with Rust from the beginning, leveraging its (memory) safety, concurrency, and exceptional performance capabilities that align with our engineering philosophy.

However, our projects often use NextJS—a React-based framework—which requires server-side rendering to optimize performance. Despite avoiding JavaScript for our primary backend services, we still need a JavaScript runtime to support server-side rendering, making runtime selection relevant in our architecture.

JavaScript Runtimes

Aside from Node.js, there are two competing JavaScript runtimes: Deno and Bun.

Bun

Built in Zig, Bun is a fast, Node-compatible, JavaScript runtime that aims to provide a better alternative to Node.js. Bun aims for 100% Node.js compatibility and is designed for speed, complete with a bundler, test runner and package manager.

Express benchmark shown on the website

Express benchmark shown on the website

Deno

Built in Rust, Deno is a secure, high-performance JavaScript runtime that aims to provide a better alternative to Node.js. Deno is designed for security, complete with a package manager, Node.js compatibility, and native TypeScript support.

Deno benchmark shown on the website

Deno benchmark shown on the website

Bun In Production

Since we’re using Next.js we originally picked Bun as our JavaScript runtime due to its speed and compatibility with Node.js (at the time Deno simply wasn’t compatible). It was definitely an improvement but we ran into two major issues:

  1. Even post 1.0, there have been several releases with sudden breaking changes.
  2. We experienced increased memory usage and out-of-memory errors, suggesting that Bun’s memory management was sub-par.

We can work around ‘unstable releases’ by testing thoroughly and adopting a more conservative approach to updates. But, we couldn’t get a grip on Bun’s memory management issues.

Bun average memory usage

Bun average memory usage

Switching To Deno

Due to the memory management issues in Bun we needed to find an alternative (or switch back to Node.js). Thankfully, with the release of Deno 2.0, we were finally able to run our application (without making any changes to our code) with Deno. And so we did.

Deno average memory usage

Deno average memory usage

Switching from Bun to Deno

Switching from Bun to Deno

Peak memory usage went down from 700 MiB to 400 MiB and most importantly, memory usage was (way) more stable. This also resulted in a significant reduction in the amount of out-of-memory errors. Additionally, since adopting Deno we haven’t experienced breaking changes in new releases.

Conclusion

Moral of the story? Benchmarks are interesting but do not accurately reflect real-world usage. What good is a fast runtime if it’s not stable? In our case, we’ve found Deno to be a great alternative to Bun. It’s fast, more stable, and uses less memory.

Latest Posts

Discover more articles in our blog

Stay up to date