Mastering JavaScript Fundamentals
Execution Context and Hoisting
Code's Hidden World
When the JavaScript engine runs your code, it doesn't just read it from top to bottom like a book. Before executing a single line, it performs a setup phase. This preparation happens inside a special environment called an Execution Context—think of it as the stage where your code performs.
Every line of JavaScript runs inside an Execution Context — this is the foundation of how JS works.
The first one created is the Global Execution Context (GEC), the default environment for any code that isn't inside a function. When you call a function, a new Function Execution Context (FEC) is created and stacked on top of the global one. This creates a hierarchy, known as the call stack, which JavaScript uses to keep track of its place in the code.
The Two-Phase Process
Every execution context is created in two distinct phases. This separation is fundamental to understanding why JavaScript sometimes seems to know about variables and functions before it reaches their declarations.
First, the engine sets up memory. Only then does it execute the code.
-
Memory Creation Phase: The engine makes a first pass through the code within the context. It doesn't execute anything. Instead, it identifies all variable and function declarations and allocates memory for them. Variables declared with
varare initialized with a placeholder value ofundefined. Function declarations are stored in their entirety. -
Execution Phase: Now the engine makes a second pass. This time, it executes the code line by line, assigning the actual values to the variables it already set up in memory. If it encounters a function call, it creates a new Function Execution Context and begins this two-phase process all over again for that function.
What is Hoisting?
This two-phase process creates a behavior known as hoisting. Because the JavaScript engine allocates memory for declarations before execution, it's as if those declarations are moved to the top of their scope. This allows you to reference them, in some cases, before they are physically declared in your code.
This process of assigning variable declarations a default value of undefined during the creation phase is called Hoisting.
However, hoisting works differently for var, let, const, and function declarations.
// Example with 'var'
console.log(myVar); // Outputs: undefined
var myVar = 10;
console.log(myVar); // Outputs: 10
In the example above, myVar isn't a surprise to the engine on the first line. During the memory creation phase, it already allocated space for myVar and gave it the initial value undefined. During the execution phase, it prints undefined, then later assigns 10.
Function declarations are hoisted completely, meaning the entire function body is stored in memory during creation. Function expressions, however, are treated like variable assignments.
| Type | Hoisting Behavior | Example |
|---|---|---|
| Function Declaration | Entire function is hoisted | sayHello(); works before function sayHello() {} |
| Function Expression | Only the variable declaration is hoisted (as undefined) | sayHi(); fails before var sayHi = function() {}; |
Modern Scopes with let and const
To create more predictable code, ES6 introduced let and const. These declarations are also hoisted, but they are not initialized with undefined. Instead, they enter a state known as the Temporal Dead Zone (TDZ).
console.log(greeting); // Throws ReferenceError: Cannot access 'greeting' before initialization
let greeting = "Hello";
The engine knows greeting exists because of hoisting, but it remains in the TDZ from the start of its scope until the line where it is declared. Accessing a variable in its TDZ results in a ReferenceError. This prevents bugs where you might accidentally use a variable before it's intentionally assigned a value.
Key takeaway:
letandconstare hoisted, but you can't access them before their declaration.varis hoisted and can be accessed, but its value will beundefined.
Understanding how execution contexts and hoisting work is a major step toward writing professional, bug-free JavaScript. It demystifies why the language behaves the way it does and gives you the tools to manage scope and variable lifecycle effectively.
Ready to check your understanding?
What are the two distinct phases of creating a JavaScript execution context?
Consider the following JavaScript code snippet:
console.log(myVar);
var myVar = 'Hello, world!';
What will be logged to the console when this code is executed?
By mastering these concepts, you move beyond just knowing the syntax and start to truly understand the JavaScript engine.