Demystifying JavaScript: Execution Contexts, Hoisting, Scopes & Closures
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 (
windowin browsers,globalin Node),this, memory for variables/functions, initializes variables toundefined, 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
argumentsobject (instead of the global object) - Its own
thisbinding - 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