What a runtime is and why code does not work everywhere the same way

There is one thing that confuses beginners often: you write JavaScript, it works perfectly in the browser — but when you try to run it on a server, nothing happens. Or vice versa: a script runs in Node.js but crashes in the browser complaining about a missing function.

The problem is not the code. The problem is where that code runs. And that “where” is the runtime (execution environment).

Simple analogy: JavaScript is a language. And the runtime is the country where you speak it. The grammar is the same, but the laws, culture, and available services are different.

The problem: why “it works for me” is not the same thing

JavaScript was originally created for browsers. It was a language that lived inside web pages, could manipulate the DOM, react to clicks, and change colors. But it couldn’t read files, couldn’t talk to the network directly, and had no access to the filesystem.

Then Node.js came along — and JavaScript went beyond the browser. On the server it gained access to files, networking, and databases — but it lost the DOM. There is no window or document here. It’s a different world.

Then others arrived: Bun, Deno, Cloudflare Workers — each with their own quirks.

The same JavaScript syntax works in all of them. But the set of tools available to the code is different. And that is where the errors happen.

How this works in plain language

A runtime is three things:

1. The JavaScript engine

This is what actually understands and runs your JavaScript code. Most modern runtimes use one of three main engines:

The engine parses (reads) your code, transforms it into machine instructions, optimizes, and executes it.

2. Global objects and APIs

This is where runtimes start to differ:

In the browser:

window          // global browser object
document        // DOM — the entire page
fetch()         // network requests
setTimeout()    // timers
localStorage    // client-side data storage

In Node.js:

global          // global object (instead of window)
require()       // file imports
fs              // filesystem access
http            // server creation
process         // process information (PID, environment variables)

In Bun:

Bun.serve()     // built-in web server
Bun.file()      // convenient file handling
fetch()         // built-in, no extra dependencies needed

Core JavaScript (variables, functions, classes) works the same everywhere. But the APIs do not.

3. Module system and dependencies

Each runtime handles the question of “how do I import code from another file?” differently:

Why this matters in practice

1. Picking the right tool for the job

If you’re building a server — Node.js or Bun will work. If you’re working with frontend code — the browser itself is the runtime. If you’re running functions in the cloud — maybe Cloudflare Workers or AWS Lambda.

There is no “correct” runtime. There is the right one for your specific task.

2. Understanding why code fails

When you understand the difference between runtimes, the nature of many errors becomes obvious:

3. Performance

Different runtimes have different performance characteristics. Node.js is mature with the largest ecosystem. Bun has significantly faster startup and less memory usage but is younger. Deno has built-in security and TypeScript out of the box.

For a small script, performance doesn’t matter much. For a high-load server — it might be critical.

A practical example: the same code, different runtimes

Here’s a simple file:

// hello.js
console.log("Hello!");
console.log(typeof window);    // browser API?
console.log(typeof process);   // Node.js API?
console.log(typeof Bun);       // Bun API?

Running in the browser (via devtools console):

Hello!
object      // window — browser object
undefined   // process — not available
undefined   // Bun — not available

Running in Node.js:

Hello!
undefined   // window — not available
object      // process — available
undefined   // Bun — not available

Running in Bun:

Hello!
undefined   // window — not available
object      // process — available (Bun emulates Node.js APIs)
object      // Bun — available

Same code, three different results. Not because of a bug — because of different environments.

How to pick a runtime for your project

The question is not which runtime is “best.” The question is what you need:

Node.js — if:

This is the default choice. It is not always the fastest, but it is always the safest.

Bun — if:

Bun is gaining popularity but it is younger. Not everything on npm works with it without issues.

Deno — if:

Deno is conceptually interesting but its ecosystem is smaller.

Common mistakes

1. Assuming every npm package works everywhere

Not every npm package is universal. If a package relies on fs or process, it won’t work in the browser or in Cloudflare Workers. Always check the package documentation.

2. Confusing the language with the runtime

JavaScript is not Node.js. Those are two different things. JavaScript is the language (syntax, operators, functions). Node.js is one of the environments where that language runs, and it adds its own APIs.

3. Ignoring versions

Node.js 14, 18, 20 — these are different runtimes with different capabilities. What works in Node.js 20 might not work in 14. Always check the minimum supported version.

4. Using the wrong runtime for the task

The browser is not suitable for server-side logic. Node.js is not suitable for frontend rendering (without extra tooling). Cloudflare Workers are not suitable for long-running tasks.

Conclusion / action plan

The runtime determines which tools your code has access to. Understanding that difference is the key to making “it works” a reliable outcome rather than a surprise.

Here is what to do next:

  1. Figure out which runtime you are currently using (browser, Node.js, or something else).
  2. Run a simple JS file in two different runtimes and compare the output.
  3. Check whether your dependencies actually work in your chosen runtime.
  4. If the project is new — consider alternatives (Bun, Deno) and benchmark them.
  5. Add a runtime version check to your build scripts to avoid surprises.

Once you stop thinking “JavaScript is the same everywhere” and start thinking “which runtime do I need” — your understanding jumps noticeably.