Demystifying JavaScript: Execution Contexts, Hoisting, Scopes & Closures

Published: (January 20, 2026 at 05:42 AM EST)
2 min read
Source: Dev.to

Source: Dev.to

What Are Execution Contexts?

Execution Contexts manage code complexity for the JavaScript engine, similar to how functions manage authoring complexity. The engine creates them in two types:

  • Global Execution Context – created at startup.
  • Function Execution Context – created each time a function is invoked.

Each context has a Creation phase and an Execution phase.

  • Creation: the engine sets up the global object (window in browsers, global in Node), this, memory for variables/functions, initializes variables to undefined, and loads function declarations fully.

Hoisting Explained

Hoisting occurs during the Creation phase. Variable declarations are set to undefined, while function declarations are fully loaded before line‑by‑line execution. This explains why the following code logs undefined for name:

console.log('name: ', name);          // undefined
console.log('getUser: ', getUser);    // [Function: getUser]

var name = 'Tyler';

function getUser() {
  return { name: name };
}

Function declarations are hoisted completely, unlike variables.

Function Execution Contexts

When a function is invoked, a new Function Execution Context is created. It includes:

  • An arguments object (instead of the global object)
  • Its own this binding
  • Hoisted local variables and function declarations

Invocations push new contexts onto the call stack; completion pops them off, reflecting JavaScript’s single‑threaded nature.

Arguments become local variables, and inner variables stay within their own context. For example, passing handle to getURL(handle) adds it locally alongside globals.

Scopes and Scope Chain

A scope defines variable accessibility for the current Execution Context. Local variables cannot be accessed after their context is popped, leading to a ReferenceError if you try to use them later:

function foo() {
  var bar = 42;
}
foo();
console.log(bar); // ReferenceError

If a variable isn’t found locally, the engine climbs the Scope Chain to parent contexts, up to the global scope. When multiple name variables exist in different contexts, the engine resolves them locally first, producing outputs such as undefined, Jordyn, Jake, Tyler depending on the lookup order.

Closures in Action

A closure allows an inner function to retain access to variables from its outer (parent) scope even after the parent function has finished executing. In the example below, the returned inner function keeps a reference to x:

function makeAdder(x) {
  return function inner(y) {
    return x + y;
  };
}

const add5 = makeAdder(5);
console.log(add5(2)); // 7

The inner function forms a closure over x, preserving its value across calls.

For more information, visit:
Ultimate Guide to Execution Contexts, Hoisting, Scopes, and Closures in JavaScript

Back to Blog

Related posts

Read more »

What Happens Before the Event Loop

JavaScript code is not executed natively by an engine the moment a task appears in the call stack. Most articles focus on how the Event Loop works, leaving out...

The Secret Life of JavaScript: Memories

The Ghost Room: A Story of Closures and the Variables That Refuse to Die. Timothy stood in the doorway of a small, private study off the main library hall. He h...