Javascript JS Foundation & Core Concepts How JavaScript Runs

You’ve written your first few lines of JavaScript. Maybe you followed along with our Introduction to JavaScript article and got some code working. Things are starting to click. But here’s a question most beginner tutorials quietly skip over: what actually happens between the moment you type console.log("Hello") and the moment it shows up in your browser?

I get why tutorials skip it. It sounds intimidating. “Engines,” “runtime,” “call stack” — these feel like senior-developer vocabulary. But here’s the thing: once you actually understand this, you’ll read error messages differently. You’ll debug faster. You’ll stop being surprised when JavaScript does something unexpected. This knowledge pays back quickly, and it’s honestly not that hard once someone just explains it properly.

So let’s do that. No jargon walls. No unnecessary theory. Just a clear explanation of how JavaScript runs, from your code all the way down to actual execution, with visuals and examples to make it stick.


01What Exactly Is a JavaScript Engine?

Think of a JavaScript engine as the translator between the code you write and the machine that actually runs it. Your computer doesn’t understand JavaScript. It understands machine code — a very low-level set of instructions that are specific to the processor. An engine bridges that gap.

More precisely, a JavaScript engine is a program (or part of a program) that reads your JavaScript code, understands what it means, and executes it. Every browser ships with one. Node.js has one too. Without an engine, your .js file is just a text file with no power behind it.

💡

A quick analogy

If JavaScript is a recipe written in English, and your computer only speaks binary, the JavaScript engine is the chef who reads the recipe and actually cooks the dish. It understands the instructions and translates them into real action.


02The Two Engines You’ll Hear About the Most

There are a few JavaScript engines out there, but two come up constantly in conversations about how javascript engine works in practice.

V8: The Engine Behind Chrome and Node.js

V8 is built by Google. It powers Google Chrome and, importantly, it’s the engine that Node.js is built on top of. V8 is open-source, written in C++, and is often considered one of the fastest JS engines available today. When people talk about JavaScript being “fast now,” V8’s optimization work is a huge reason why.

SpiderMonkey: Mozilla’s Engine for Firefox

SpiderMonkey was actually the very first JavaScript engine, created by Brendan Eich, the same person who invented JavaScript. It now powers Firefox. Fun fact: its name isn’t some scary reference, it just came from a coffee place Brendan liked while building it.

Others Worth Knowing

Safari uses JavaScriptCore (also called Nitro) from the WebKit project. Microsoft Edge used to have its own engine called Chakra, but switched to V8 when it rebuilt Edge on Chromium. The point is: every browser environment has an engine, and they all follow the same rules defined by the ECMAScript specification, which is why code you write runs the same way across browsers.

🔎

Why Does This Matter?

You’ll see “V8” mentioned in Node.js docs and Chrome DevTools. Knowing that V8 is the javascript execution engine running your code helps you understand error messages, performance tips, and why certain debugging tools work the way they do.


03Inside the Engine: The Journey from Code to Execution

This is the part most tutorials either skip or make way too complicated. Let’s walk through what actually happens inside the engine when it sees your code. There are a few clear stages.

The JavaScript Execution Pipeline

📄

Source Code

Your .js file as plain text

🔤

Tokenizer / Lexer

Breaks code into tokens

🌳

Parser / AST

Builds a tree structure

JIT Compiler

Turns AST into machine code

🚀

Execution

Code actually runs

Step 1: Tokenization (Breaking Code into Pieces)

The engine’s first job is to read your source code as raw text and break it into meaningful chunks called tokens. This is handled by the tokenizer (sometimes called the lexer).

Take a simple line like this:

let result = 5 + 3;

To the tokenizer, that line becomes a list of individual tokens, each tagged with a type:

Tokens recognized from: let result = 5 + 3;

let
result
=
5
+
3
;

Each colored block above is a token. Keyword, identifier, operator, number literal, punctuation. The tokenizer doesn’t think about meaning yet, it just categorizes pieces of text.

Step 2: Parsing and the Abstract Syntax Tree

Once the code is tokenized, the parser takes those tokens and builds an Abstract Syntax Tree, or AST. This is a tree structure that represents the grammatical structure of your code in a way the engine can reason about.

Think of it like diagramming a sentence in school. “The dog barked” has a subject (the dog) and a verb (barked), and a grammar diagram would show that relationship. An AST does the same thing for code.

You can actually see what an AST looks like for any code snippet using a tool like AST Explorer. It’s worth having a look at some point, it makes this concept instantly visual.

🌳

Why is it “abstract”?

Because it doesn’t represent every single character from the source code (like spaces or parentheses). It only captures the structural meaning of the code. The syntax details that don’t affect meaning get dropped.

Step 3: JIT Compilation (This Is Why JavaScript Isn’t Slow Anymore)

Years ago, JavaScript engines were purely interpreted. They would read the AST line by line and execute as they go. That was predictable but slow.

Modern engines like V8 use something called JIT compilation (Just-In-Time compilation). The engine compiles code to machine code right before it runs, not ahead of time. And here’s the clever part: if the engine notices that certain code runs a lot (like a loop), it spends extra time optimizing that specific code to run even faster.

This is why a loop that runs a million times in JavaScript can be surprisingly fast. The engine identifies it as “hot code” and optimizes it in the background while your program is already running. This is called speculative optimization, and it’s one of the main reasons modern javascript execution performance is impressive.

// This loop will be optimized aggressively by V8 after a few iterations
let sum = 0;
for (let i = 0; i < 1_000_000; i++) {
  sum += i;
}
console.log(sum); // 499999500000

04The Call Stack: How JavaScript Tracks What's Running

This is the one concept that changes how you read error messages forever. Once it clicks, you'll never look at a stack trace the same way again.

The call stack is how the JavaScript engine keeps track of where it is in your program. Every time a function is called, the engine pushes a frame onto the stack. When that function finishes, the frame is popped off. The engine always knows "what function am I currently in" by looking at the top of the stack.

The call stack follows a LIFO structure, meaning Last In, First Out. Imagine a stack of plates: you can only add or remove from the top. The last function you called is always the first one to finish and get removed.

Let's See the Call Stack in Action

The best way to understand this is to step through it visually. Here's the code we'll use:

function add(a, b) {
  return a + b;
}

function calculate() {
  let result = add(5, 3);
  return result;
}

let answer = calculate();
console.log(answer); // 8

Hit the "Next Step" button below to walk through each stage and watch the call stack build up and unwind:

Interactive Call Stack VisualizerLive Demo
Code
function add(a, b) { return a + b;}function calculate() { let result = add(5, 3); return result;}let answer = calculate();console.log(answer); // 8
Call Stack (top = currently running)
Stack is empty
Click Next Step to start stepping through the execution.
Step 0 / 6

Notice how the stack grows as functions are called, and shrinks as they return. That's exactly what the engine is doing internally, just at a much faster speed. Every function call adds a frame. Every return removes one.

Stack Overflow: When the Stack Gets Too Full

You've probably seen this error before and wondered what it means:

// This function calls itself forever
function countdown() {
  countdown(); // calls itself without any stopping condition
}

countdown();
// Uncaught RangeError: Maximum call stack size exceeded

This is called infinite recursion. The function calls itself forever, pushing a new frame onto the stack each time. The stack has a size limit (the exact limit varies by browser and environment). Once that limit is hit, the engine throws the "Maximum call stack size exceeded" error and stops execution. This is what people mean when they say the stack "overflowed."

⚠️ Stack Overflow: What It Looks Like
countdown() — frame #1
countdown() — frame #2
countdown() — frame #3
countdown() — frame #4
countdown() — frame #...
countdown() — frame #∞ → 💥 Stack Overflow!
RangeError: Maximum call stack size exceeded

Every time you see that error in your console, you now know exactly what happened: something kept pushing frames onto the call stack without ever popping them off. Usually it's a missing base case in a recursive function, or an accidental circular function call.

The Fix: Always Give Recursion a Way Out

If you're writing a recursive function (a function that calls itself), always define a base case — a condition that stops the recursion. Without it, the call stack fills up and crashes.

// Fixed: base case stops the recursion
function countdown(n) {
  if (n <= 0) {
    console.log("Done!");
    return; // base case: stops here
  }
  console.log(n);
  countdown(n - 1); // keeps going, but with a smaller n each time
}

countdown(5);
// 5, 4, 3, 2, 1, Done!

05The JavaScript Runtime Environment: The Bigger Picture

The engine handles parsing and execution. But when you're actually running JavaScript in a browser, there's a bigger system around it called the JavaScript runtime environment. The engine is just one part of it.

Here's what the full javascript runtime environment looks like:

The Complete JavaScript Runtime Environment

JavaScript Runtime (Browser)
JavaScript Engine (V8)
📚 Call Stack
🗄️ Memory Heap
Web APIs (Browser Provided)
setTimeoutfetchDOMlocalStorageaddEventListener
Task Queue (Callback Queue)
callback Acallback B...
🔄
Event Loop

Let's break down the parts you haven't seen yet.

The Memory Heap

The engine has two memory zones. The call stack (which you know now) and the memory heap. The heap is where objects and functions are stored. When you create an array or an object in JavaScript, it gets stored in the heap. The call stack stores the reference to that object, but the actual data lives in the heap.

Web APIs: Things the Browser Provides

Here's something many beginners don't realize: functions like setTimeout, fetch, and document.querySelector are not part of JavaScript the language. They're provided by the browser through its Web APIs layer. The engine hands these tasks off to the browser, which handles them in the background.

console.log("1 - This runs first");

setTimeout(function() {
  console.log("3 - This runs after the delay");
}, 1000);

console.log("2 - This runs second");

// Output order:
// 1 - This runs first
// 2 - This runs second
// 3 - This runs after the delay

This output surprises a lot of people at first. Why does "2" print before "3" even though setTimeout is called before the third console.log? Because setTimeout is handled by the browser's Web API, not the engine directly. Once the timer finishes, the callback goes into the Task Queue.

JavaScript Is Single-Threaded

The call stack can only do one thing at a time. JavaScript is single-threaded, meaning there is only one call stack. The engine can't run two pieces of code at the same moment. It processes one operation, finishes it, and moves to the next.

This is a crucial thing to understand about the javascript runtime environment. You'll often hear that JavaScript is "non-blocking" even though it's single-threaded. That's where the Event Loop comes in.

The Event Loop (A Brief Introduction)

The event loop has one job: it watches the call stack and the task queue. When the call stack is empty (meaning the engine has nothing left to run), the event loop picks the next callback from the task queue and pushes it onto the stack.

This is how asynchronous JavaScript works. The engine doesn't wait for a timer or a network request to finish. It offloads the task to the browser's Web APIs, keeps running other code, and picks up the callback later when the stack is free.

We'll go deep on the Event Loop, Promises, and async/await in upcoming articles in this series. But knowing it exists, and roughly what it does, is important even at this early stage.

⚠️

Don't Block the Call Stack

Because JavaScript is single-threaded, if you write code that takes a very long time to run (like a massive loop or a slow synchronous operation), everything else pauses. The page freezes. Buttons stop working. This is what people mean by "blocking the main thread." We'll cover how to handle this with async code later in the series.


06Reading Stack Traces Like a Developer

Now that you understand the call stack, let's make one thing immediately practical. When your code throws an error, the browser shows you a stack trace. This is just the call stack at the moment the error happened, printed out for you to read.

Here's an example:

function formatName(user) {
  return user.name.toUpperCase(); // What if user is undefined?
}

function displayUser(user) {
  let name = formatName(user);
  console.log(name);
}

displayUser(undefined); // Passing undefined on purpose

This will throw an error like:

TypeError: Cannot read properties of undefined (reading 'name')
    at formatName (script.js:2)
    at displayUser (script.js:6)
    at script.js:9

Read that stack trace from bottom to top. Line 9 called displayUser. Inside displayUser, line 6 called formatName. And inside formatName, line 2 is where things blew up. The stack trace is literally showing you the call stack at the time of the error. You now know exactly how to read it.


07Why This Actually Makes You a Better Developer

Let me be honest: when I first learned about engines and the call stack, I didn't immediately think "wow, I'll use this tomorrow." But then I went to debug something and suddenly I was reading stack traces without confusion. I could explain to myself why setTimeout behaves the way it does. I knew what "blocking the main thread" actually meant.

Understanding how javascript code runs under the hood makes you less reactive and more confident. You stop guessing and start reasoning. That's a big shift, and it happens gradually as this foundation builds up.

Here's a quick mental checklist of what you now know:

What You Now Understand

A JavaScript engine (like V8) is a program that parses and runs your code. Your code goes through tokenization, parsing (AST), JIT compilation, and then execution. The call stack tracks which function is currently running using LIFO order. JavaScript is single-threaded: one thing at a time. The runtime environment includes the engine, Web APIs, a task queue, and the event loop. Stack overflow errors happen when the call stack fills up, usually from infinite recursion. Stack traces in error messages show you the call stack at the moment of failure.


08What's Coming Next

In the next article in this JavaScript series, we'll set up your actual development environment and write your very first JavaScript program properly — using the browser console, an HTML script tag, and Node.js. All three ways, so you know your options from day one.

After that we'll move into JavaScript syntax, variables, comments, and how ECMAScript versioning works. The foundation is being laid carefully, and each article builds on the last. If you've been following along from the start, you're in a good place.

The engine is running. The stack is clear. Let's keep going.

How JavaScript Runs

Choose a difficulty to load your challenge

Loading challenge…

Ready to Practice?

Select a difficulty level above to load your challenge.

Easy

1
Live Preview
Great Work!