Skip to content

How JavaScript Works Behind the Scenes

You’ve written variables, functions, and conditionals. Your code runs and produces output. But have you ever wondered what actually happens when JavaScript executes your code?

Where do variables live before they’re assigned? Why can you call a function before it’s defined in some cases? What creates that “environment” where your code runs?

This article pulls back the curtain and shows you how JavaScript runs your code under the hood.

Imagine you’re directing a play. Before the actors say a single line, the crew has to set up the stage. They place props, mark positions, and prepare everything. Only after that does the actual performance begin.

JavaScript works in a similar way. Before any line of your code runs, the engine sets up an environment. It reserves space for variables, stores function definitions, and prepares the stage. Then, and only then, does it start executing your code line by line.

That setup and execution happen inside something called an Execution Context.

An Execution Context is the environment where your JavaScript code runs. It holds everything the code needs: variables, functions, and a reference to the outer scope.

Whenever you run JavaScript:

  1. A Global Execution Context is created for your entire script.
  2. Each time you call a function, a new Function Execution Context is created for that function.

Think of it as a sandbox. The global context is the main sandbox. Each function gets its own smaller sandbox when it runs, with its own variables and parameters.

JavaScript doesn’t just run your code top to bottom in one pass. It works in two phases:

  1. Creation Phase (also called the Memory Phase)
  2. Execution Phase

Let’s break each one down.

In the Creation Phase, JavaScript sets up the environment before running a single line of your code.

It does the following:

  1. Creates the Global Object: In browsers, this is window. In Node.js, it’s global. Your entire script runs in the context of this object.

  2. Sets up this: The this keyword is bound to the global object (or the object that owns the current context).

  3. Creates the Memory Heap: A space where variables and function references are stored.

  4. Hoists variables and functions: All var declarations and function declarations are scanned. Variables are initialized to undefined. Functions get their full definition stored.

This is why you might have seen “weird” behavior like this:

console.log(name); // undefined (not an error!)
var name = "John";

The variable name exists in memory during the Creation Phase, but it holds undefined until the Execution Phase assigns "John" to it. This behavior is called hoisting.

For let and const, the variable exists but is in a “temporal dead zone” until the line that declares it runs. Accessing it before that throws a ReferenceError.

In the Execution Phase, JavaScript runs your code line by line. It assigns values to variables, executes function calls, and evaluates expressions.

When the engine hits a function call, it pauses the current execution, creates a new Execution Context for that function, runs through both phases for that function, and then returns the result back to where it was called.

Let’s trace through a simple example step by step.

let val1 = 10;
let val2 = 5;

function addNum(num1, num2) {
  let total = num1 + num2;
  return total;
}

let result1 = addNum(val1, val2);
let result2 = addNum(10, 2);

Before any code runs, the engine sets up the global context:

Variable/Function Initial Value
val1 undefined
val2 undefined
addNum function definition
result1 undefined
result2 undefined

All variables are in memory with undefined. The function addNum is stored in full.

Now the code runs line by line:

Line 1: val1 = 10
Line 2: val2 = 5
Line 3-7: function addNum -> skipped (already in memory)
Line 9: result1 = addNum(val1, val2) -> function call!

When the engine hits addNum(val1, val2), it pauses and creates a new Execution Context for the function.

Step 3: Function Context for addNum(val1, val2) (First Call)

Section titled “Step 3: Function Context for addNum(val1, val2) (First Call)”

Creation Phase for this function:

Variable Initial Value
num1 undefined
num2 undefined
total undefined

Execution Phase for this function:

num1 = 10 (from val1)
num2 = 5 (from val2)
total = 10 + 5 = 15
return 15

The function returns 15 to the global context. This function’s Execution Context is then removed from the stack. Control returns to the global context.

result1 = 15

Step 4: Function Context for addNum(10, 2) (Second Call)

Section titled “Step 4: Function Context for addNum(10, 2) (Second Call)”

Creation Phase for this function:

Variable Initial Value
num1 undefined
num2 undefined
total undefined

Execution Phase for this function:

num1 = 10
num2 = 2
total = 10 + 2 = 12
return 12

The function returns 12. Its context is removed. Control returns to global.

result2 = 12

The global execution continues. Both result1 and result2 now have their values. When the script finishes, the Global Execution Context is also cleared.

JavaScript uses a Call Stack to manage Execution Contexts. Think of it as a stack of plates:

  • When a function is called, its context is pushed onto the stack.
  • When the function returns, its context is popped off the stack.
  • The context on top of the stack is the one currently running.

This is why you see a stack trace when an error occurs. It shows you the chain of function calls that led to the error.

Understanding Execution Context helps you make sense of:

  • Hoisting: Why var and function declarations seem to “move” to the top.
  • Scope: Why variables inside a function aren’t visible outside.
  • Closures: How functions “remember” the environment they were created in (we’ll cover this in a later article).

You don’t need to think about Execution Context every time you write code. But when something behaves unexpectedly, this mental model will help you debug it.

JavaScript runs your code in two phases: Creation and Execution. Before any code runs, the engine sets up an Execution Context, reserves space for variables, and stores function definitions. Then it executes your code line by line. Each function call gets its own Execution Context, which is managed by the Call Stack.

You learned how to:

  • Understand what an Execution Context is
  • See how the Creation Phase hoists variables and functions
  • Trace through the Execution Phase step by step
  • Relate this to hoisting, scope, and the Call Stack

Keep this model in mind when you encounter hoisting quirks or scope-related bugs. It will save you a lot of head scratching.