Skip to main content
When you run a JavaScript program, several distinct pieces of infrastructure collaborate to make execution happen. Understanding each layer — the engine, the runtime, and the call stack — gives you a mental model for why JavaScript behaves the way it does, and equips you to reason about performance, errors, and asynchrony.

The JavaScript engine

A JavaScript engine is the program that reads your source code and executes it. The most widely known engine is V8, developed by Google and used in both Chrome and Node.js. Other engines include SpiderMonkey (Firefox) and JavaScriptCore (Safari). At a high level, the engine has two core components:
  • The heap — an unstructured region of memory where objects are allocated dynamically.
  • The call stack — an ordered data structure that tracks which function is currently executing and where execution should return after each function completes.
Modern engines like V8 do not simply interpret your code line by line. They use a just-in-time (JIT) compilation pipeline:
  1. The source code is parsed into an abstract syntax tree (AST).
  2. An interpreter (Ignition in V8) converts the AST into bytecode and begins executing it.
  3. A profiler monitors which functions are called frequently (“hot” paths).
  4. The optimising compiler (TurboFan in V8) compiles hot functions into highly optimised machine code.
  5. If assumptions made during optimisation turn out to be wrong, V8 deoptimises back to interpreted bytecode.
You don’t need to manage JIT compilation manually. But knowing it exists helps explain why JavaScript performance can vary significantly between code patterns that look superficially similar.

The heap

The heap is where JavaScript allocates memory for objects, arrays, closures, and other reference types. When you write:
const user = { name: "Alice", age: 30 };
The object { name: "Alice", age: 30 } is stored on the heap. The variable user holds a reference — a pointer — to that location in memory. Primitive values like numbers, strings, and booleans are typically stored by value, either on the stack or inlined directly. The heap is managed automatically by the garbage collector, which periodically reclaims memory that is no longer reachable. You will learn more about garbage collection in the Garbage Collection article.

The call stack

The call stack is a LIFO (last-in, first-out) data structure. Every time you call a function, a new stack frame is pushed onto the top of the stack. That frame records:
  • The function’s local variables and arguments.
  • The return address — where execution should resume after the function finishes.
When a function returns, its frame is popped off the stack, and control returns to the caller.
function greet(name) {
  const message = buildMessage(name);
  return message;
}

function buildMessage(name) {
  return `Hello, ${name}!`;
}

console.log(greet("Alice"));
Here is how the call stack evolves when you run this code:
1

main() is pushed

The JavaScript engine starts executing the script. An implicit main frame is placed on the stack.
2

greet('Alice') is pushed

greet is called. A new frame for greet is pushed on top of main. Local variable name = "Alice" is recorded in this frame.
3

buildMessage('Alice') is pushed

Inside greet, buildMessage is called. Its frame is pushed on top. The template literal is evaluated and the string "Hello, Alice!" is produced.
4

buildMessage returns

buildMessage finishes. Its frame is popped. Control returns to greet, which receives the return value.
5

greet returns

greet finishes. Its frame is popped. Control returns to main with the string "Hello, Alice!".
6

console.log runs

console.log is called, prints the string, and its frame is immediately popped. The stack is now empty.

Stack overflow

Because the call stack has a fixed maximum size (typically a few thousand frames depending on the environment), deeply or infinitely recursive functions will exhaust it:
function infinite() {
  return infinite(); // no base case
}

infinite(); // RangeError: Maximum call stack size exceeded
A RangeError: Maximum call stack size exceeded error almost always means you have unbounded recursion. Check that every recursive function has a reachable base case.

The runtime

The JavaScript engine itself is deliberately minimal — it executes JS code but provides almost no I/O capabilities on its own. The runtime wraps the engine and adds the environment-specific APIs that make JavaScript practically useful. In the browser, the runtime includes:
  • Web APIssetTimeout, fetch, XMLHttpRequest, the DOM, localStorage, WebSockets, and so on. These are implemented in C++ by the browser, not by the engine.
  • The event queue (callback queue) — a list of callbacks waiting to run once the call stack is empty.
  • The microtask queue — a higher-priority queue used for Promise callbacks and queueMicrotask.
  • The event loop — the mechanism that coordinates all of the above.
In Node.js, the runtime swaps out browser-specific Web APIs for Node-specific equivalents (fs, http, process, etc.), and uses libuv to handle asynchronous I/O instead of the browser’s event infrastructure.
When you call setTimeout(fn, 1000), the engine hands the timer off to the Web API layer. The engine itself is free to continue executing other code. After 1000 ms, the Web API places fn onto the callback queue, where the event loop will eventually pick it up and push it onto the call stack.

Putting it all together

console.log("start");

setTimeout(function onTimeout() {
  console.log("timeout");
}, 0);

console.log("end");
Even though the timeout delay is 0, the output is:
start
end
timeout
This is because setTimeout is a Web API call. The callback onTimeout is handed to the browser’s timer API, and even with a zero delay, it is placed into the callback queue only after the current call stack is fully empty. The event loop then picks it up. You will explore this in depth in the Event Loop article.

Summary

ComponentRole
JS Engine (V8)Parses, compiles, and executes JavaScript
HeapAllocates memory for objects and closures
Call stackTracks the current execution position (LIFO frames)
Web APIsProvides browser or Node.js built-in capabilities
Event / task queueHolds callbacks ready for execution
Event loopMoves callbacks from queue to stack when stack is empty
Understanding this foundation makes every other JavaScript concept — closures, promises, async/await, garbage collection — significantly easier to reason about.