Code & Development

Runtime: The Concept Nobody Explained Properly (And That Changes Everything When You Understand It)

Runtime means both the moment of execution and the environment that runs the code. Understand this duality, learn to differentiate compile-time errors from runtime errors, and see how Node, Deno, and Bun apply this concept in distinct ways.

Runtime: The Concept Nobody Explained Properly (And That Changes Everything When You Understand It)

The confusion has a reason

Have you ever read a sentence like this?

"Node.js is a JavaScript runtime."

And thought: okay, but what does that mean in practice?

If you had that feeling, it's not because you weren't paying attention. It's because most technical content assumes you already know what "runtime" means — and moves on as if it were obvious.

But it's not obvious. And the fact that no one stops to explain it properly creates a ripple effect: you read about Node, about Deno, about Bun, about "runtime errors", about "runtime environment" — and all these things seem vaguely related, but you can't piece them together.

I know how that feels. For a long time, I used the word "runtime" without being sure what it meant. I repeated what I read. It worked in conversations. But deep down, the concept was a fog.

This article exists to dispel that fog. Without unnecessary jargon. Without skipping steps. Without pretending it's simple when it actually needs a bit of context to make sense.

Runtime as a moment: when code comes to life

All code goes through at least two phases before doing anything useful.

The first phase is when you write the code. It exists as text in a file. At this point, it does nothing — it's just an instruction, like a cake recipe that no one has started preparing yet.

The second phase is when something takes that code and starts executing it. Instructions become actions. Variables receive values. Functions are called. Results appear.

This second phase is the runtime. Literally: the time when the code is running.

It seems trivial, but this distinction has enormous practical consequences:

JAVASCRIPT
function dividir(a, b) {  return a / b;}const resultado = dividir(10, 0);console.log(resultado); // Infinity

When you wrote this code, nothing went wrong. Your editor didn't complain. JavaScript didn't point out any errors. Everything seemed to work.

But when the code ran — at runtime — it produced Infinity, which is probably not what you wanted. The problem only exists at the moment of execution, because only at that moment do the real values (10 and 0) encounter the division operation.

Now look at this one:

JAVASCRIPT
function saudar(nome) {  return `Olá, ${nme}!`; // typo: 'nme' instead of 'nome'}

This code has a bug. But if no one calls the function saudar, the error will never appear. It only explodes at runtime — the moment someone actually executes saudar("Maria") and JavaScript tries to find a variable called nme that doesn't exist.

Runtime vs. compile time

When you are writing code in VS Code, you are in development time. At this moment, some tools — like TypeScript or ESLint — can look at your code and point out problems before it runs.

TYPESCRIPT
function saudar(nome: string): string {  return `Olá, ${nme}!`; // TypeScript will underline 'nme' in red here}

TypeScript caught the error before runtime because it statically analyzes the code — it reads the text, checks types, compares references. This happens in what we call compile time.

But not every error can be caught beforehand:

TYPESCRIPT
function buscarUsuario(id: number): string {  const usuarios: Record<number, string> = {    1: "Ana",    2: "Carlos",  };  return usuarios[id]; // What if 'id' is 3?}const nome = buscarUsuario(3);console.log(nome.toUpperCase()); // 💥 TypeError at runtime!

TypeScript doesn't complain here. The typing is correct. But at runtime, when id is 3, the result is undefined — and calling .toUpperCase() on undefined explodes.

Runtime as an environment: the machine that runs your code

When someone says "Node.js is a JavaScript runtime," they are not talking about a moment in time. They are talking about a program — a complete environment that knows how to take JavaScript code and execute it.

JavaScript, by itself, is just a language. A set of rules on how to write instructions. But rules alone do nothing. You need something that reads these rules and puts them into practice.

Every runtime is a combination of two things:

Anatomy of a runtime
🔧
Engine

The part that understands the language and executes instructions. V8 in Chrome and Node. SpiderMonkey in Firefox. JavaScriptCore in Safari and Bun.

📦
APIs

The extra capabilities that define what the code can do in the world. In the browser: document, window, fetch. In Node: fs, http, process.

The engine is the motor. The APIs are the tools that come in the box. Together, they form the environment where your code comes to life.

The same code, different runtimes

The same JavaScript behaves differently depending on where it's running — because the runtime changes the rules of the game.

In the browser:

JAVASCRIPT
// This works in the browserdocument.querySelector('.botao').addEventListener('click', () => {  alert('Clicked!');});

In Node.js:

JAVASCRIPT
// This works in Nodeconst fs = require('fs');const conteudo = fs.readFileSync('./arquivo.txt', 'utf-8');console.log(conteudo);

If you try to run the first code in Node, it explodes. document doesn't exist there. If you try to run the second in the browser, same result — require and fs don't exist in the browser. JavaScript is the same. The language is the same. But what's available changes completely depending on the runtime.

The new generation of runtimes: Node vs. Deno vs. Bun

Node.js dominated the landscape for over a decade. But in recent years, two new runtimes appeared with different proposals. Now that you understand what a runtime is, comparing the three becomes simple — because the question shifts from "which is the best?" to "which engine does it use, what APIs does it offer, and what philosophy does it follow?".

Node.js — the veteran

Created in 2009 by Ryan Dahl. Takes V8 (Chrome's engine) and runs it outside the browser. Instead of document and window, it offers fs, http, process and dozens of other modules. It has the largest ecosystem in the world — over 2 million packages on npm.

JAVASCRIPT
// Node.js — creating an HTTP server in 5 linesconst http = require('http');const server = http.createServer((req, res) => {  res.end('Hello from Node!');});server.listen(3000);

Deno — the fresh start

Created in 2020 by the same Ryan Dahl. After years of seeing how Node was used, he gave a talk called "10 Things I Regret About Node.js" and started from scratch. Same V8 engine, but everything else is different: native TypeScript, security by default, built-in tools.

TYPESCRIPT
// Deno — same server, native TypeScript, no configDeno.serve({ port: 3000 }, (_req) => {  return new Response("Hello from Deno!");});

Bun — raw speed

Appeared in 2023 with an aggressive proposal: to be the fastest JavaScript runtime. It uses a different engine — JavaScriptCore (Apple/Safari) instead of V8. Written in Zig. Starts processes up to 4x faster than Node. Installs packages up to 25x faster than npm.

JAVASCRIPT
// Bun — same server, familiar syntaxBun.serve({  port: 3000,  fetch(req) {    return new Response("Hello from Bun!");  },});
BASH
# In Bun, everything is frighteningly fastbun install  # instead of npm installbun test     # instead of jest/vitestbun build    # instead of webpack/esbuild

Comparing side by side

Node.js vs Deno vs Bun
Node.js
🟢

V8 Engine. 15+ years. Largest ecosystem. TypeScript needs compiling. No security restriction. Build your own tool stack.

Deno
🔒

V8 Engine. ~6 years. Native TypeScript. Security by default (explicit permissions). Built-in formatter, linter, and test runner.

Bun

JavaScriptCore Engine. ~3 years. Native TypeScript. 4x faster than Node on startup. Bundler, test runner, and package manager included.

Three runtimes, same language, different philosophies.

Runtime errors in practice: what changes for you

When a bug appears in your code, one of the most useful questions you can ask is: at what phase did this error occur?

Compile-time errors are gentle — they tell you exactly what's wrong before anything happens. Runtime errors are treacherous — they can hide for days until the exact combination of data makes the bug manifest.

A common real-world example:

JAVASCRIPT
async function carregarPerfil(userId) {  const resposta = await fetch(`/api/usuarios/${userId}`);  const dados = await resposta.json();  return dados.nome.toUpperCase(); // 💥 what if the API returns { erro: "not found" }?}

No tool will warn you that dados.nome might not exist. The code is syntactically perfect. But at runtime, when the API returns an object without the nome property, the .toUpperCase() explodes.

Experienced developers write defensive code — not for elegance, but for survival:

JAVASCRIPT
async function carregarPerfil(userId) {  const resposta = await fetch(`/api/usuarios/${userId}`);  const dados = await resposta.json();  if (!dados.nome) {    return 'Unknown user';  }  return dados.nome.toUpperCase();}

The question that separates beginners from intermediates

When a beginner encounters an error, the natural reaction is: "my code is wrong, I need to fix it."

When someone who understands runtime encounters the same error, the question is different: "is this error from my code, from the environment it's running in, or from the data it received?"

It could be that you are importing a module that does not exist in that runtime. It could be that the version of Node on your machine is different from the version on the server. It could be that the user's browser does not support an API you used. It could be that the environment variable was not configured in the deploy.

All these are runtime problems, not logic problems. And when you understand the difference, your ability to diagnose bugs takes a leap.

A final thought

Most concepts in programming seem difficult for two reasons: either no one explained them properly, or someone explained them using other concepts you also didn't know.

Runtime is a classic case of the first. It's not a difficult concept. It's a poorly presented concept.

Now that you know what it is — both the moment and the environment — you will start to notice this word everywhere. In documentation, in error messages, in discussions about tools. And instead of skipping over it, you will stop and understand exactly what is being said.

It's foundational. And foundation is what separates those who follow tutorials from those who understand what they are doing.