Sitemap
Rubi chauhan

Rubi chauhan

How JavaScript Works Behind the Scenes: Understanding Execution Context

4 min readOct 7, 2026

--

What really happens when you hit “run” on your JavaScript code

You write console.log("Hello"), press run, and it works. But what actually happens between your code and the output?

Understanding this makes you better at debugging, interviews, and writing predictable code. It explains hoisting, scope, and why this sometimes behaves strangely. The concept at the center of it all is the Execution Context.

JavaScript Is Synchronous and Single-Threaded

JavaScript is a synchronous, single-threaded language. It has one thread of execution, so it runs one line at a time, in order. Everything happens inside an Execution Context.

What Is an Execution Context?

An execution context is the environment in which JavaScript code is evaluated and executed. Think of it as a big box with two components:

Press enter or click to view image in full size
  • Memory component (Variable Environment): stores variables and functions as key-value pairs.
  • Code component (Thread of Execution): runs the code, one line at a time.

The Global Execution Context

When a JavaScript file starts running, the engine (like V8 in Chrome and Node.js) creates a Global Execution Context (GEC) first. It happens before any of your code runs, even for an empty file. The GEC also creates:

  • A global object (window in browsers, global in Node.js)
  • The this keyword, which points to the global object at this level

The Two Phases of Execution Context

Every execution context is created and run in two phases.

Phase 1: Memory Creation Phase. The engine scans the code and allocates memory for variables and functions.

  • Variables declared with var are stored as undefined.
  • Functions are stored with their entire code.

Phase 2: Code Execution Phase. The engine goes through the code line by line, assigns the real values to variables, and runs functions.

Let’s see it with an example:

Get Rubi chauhan’s stories in your inbox

Join Medium for free to get updates from this writer.

javascript

var n = 2;

function square(num) {
var ans = num * num;
return ans;
}

var square2 = square(n);
var square4 = square(4);

Memory creation phase (global):

n       : undefined
square : function square(num) { ... }
square2 : undefined
square4 : undefined

Code execution phase:

  1. n = 2 is assigned.
  2. square is skipped, because it's already stored.
  3. square(n) is called, so a new execution context is created for the function.
  4. The function runs, returns 4, and its context is deleted.
  5. square2 = 4. The same happens for square4 = 16.

Function Execution Context

Every time you call a function, JavaScript creates a brand new execution context for it, with its own memory phase and execution phase. Inside square(n):

  • Memory phase: num: undefined, ans: undefined
  • Execution phase: num = 2, ans = 4, then return hands control back to the caller

When the function finishes, its context is destroyed.

The Call Stack

With many functions calling each other, how does JavaScript keep track of which context is running? It uses the Call Stack, a stack that follows LIFO (Last In, First Out).

  • The Global Execution Context sits at the bottom.
  • Each function call pushes a new context on top.
  • When a function returns, its context is popped off.

Here’s the stack for this code:

function one() { two(); }
function two() { three(); }
function three() { console.log("Done"); }
one();
Press enter or click to view image in full size

Execution goes like this:

  1. one() is called and pushed onto the stack.
  2. one calls two(), which is pushed.
  3. two calls three(), which is pushed.
  4. three prints "Done" and is popped, then two, then one.
  5. Only the Global Execution Context remains. When the program ends, it is removed too.

If functions keep calling each other without ever returning (infinite recursion), the stack runs out of room and you get the famous “Maximum call stack size exceeded” error (a stack overflow).

Why This Explains Hoisting

Remember the memory creation phase? Because variables and functions are stored in memory before the code runs, you can do this:

console.log(x);   // undefined (not an error!)
sayHi(); // "Hi!"

var x = 5;
function sayHi() {
console.log("Hi!");
}

x is undefined because during the memory phase var variables are initialized as undefined. sayHi() works because the whole function was stored in memory. This behavior is called hoisting. Nothing is physically "moved up", it's just a side effect of the memory creation phase.

(let and const are also hoisted, but they stay in the Temporal Dead Zone until their line executes, so accessing them early throws a ReferenceError.)

Quick Summary

  • JavaScript is synchronous and single-threaded.
  • Everything runs inside an Execution Context, which has a memory component and a code component.
  • A Global Execution Context is created first, and every function call creates a new Function Execution Context.
  • Each context runs in two phases: memory creation, then code execution.
  • The Call Stack manages contexts using LIFO.
  • Hoisting is a result of the memory creation phase.

Conclusion

Next time you see undefined where you expected a value, or hit a stack overflow, you'll know exactly what the engine is doing. In the next post, we'll look at how JavaScript handles asynchronous code using the Event Loop, Web APIs, and the Callback Queue.

If this helped, give it a clap 👏 and follow for more JavaScript deep dives!

--

--