Internally, the JavaScript engine handles hoisting during the memory creation phase of an execution context by scanning code, allocating memory for declarations, and attaching them to a special environment object before any code execution begins.
Contrary to the common metaphor of declarations being physically 'moved' to the top of the file, hoisting is actually a more sophisticated internal process. It occurs when the JavaScript engine creates the execution context. Before running a single line of executable code, the engine goes through two distinct phases: the Creation Phase (or Memory Creation Phase) and the Execution Phase . Hoisting is the result of the work done during the Creation Phase .
During the Creation Phase, the engine performs a 'pre-scan' of the code to build the lexical environment . It identifies all variable and function declarations and sets up memory space for them. However, the way this memory is allocated and initialized differs significantly for each type of declaration. This foundational step is what makes it seem like these declarations exist before their actual line in the code is executed.
var Declarations: The engine allocates memory for the variable and immediately initializes it with a special placeholder value: undefined. This is why accessing a var variable before its declaration is safe but yields undefined .
let and const Declarations: The engine also hoists these declarations, meaning it is aware of them in the scope. However, it does NOT initialize them. The variables are placed in a 'Temporal Dead Zone' (TDZ) from the start of the block until the engine hits their declaration line during the Execution Phase . Accessing them during this time results in a ReferenceError .
Function Declarations: These are hoisted entirely. The engine stores both the function's name and its entire body (the executable code) in memory . This allows you to call a function declared this way before its actual line in the code .
Function Expressions & Arrow Functions: These follow variable hoisting rules because they are treated as variables. For example, a var function expression is hoisted and initialized to undefined, causing a TypeError if called early because it's not yet a function . A let or const function expression is hoisted but remains uninitialized in the TDZ .
After the Creation Phase, the engine starts executing code line by line.
For variables in the TDZ (let, const, class), the engine will throw a ReferenceError if a line attempts to read or write them before their declaration line is reached .
Once the execution reaches the line with the let or const declaration, the variable is initialized with the specified value (or undefined) and removed from the TDZ, becoming safe to use .
When a function call is encountered, a new Function Execution Context is created for it, which also goes through its own Creation and Execution phases, hoisting any declarations inside that function .
It's important to note that the term 'hoisting' is not a formally defined concept in the ECMAScript specification . It's a widely-used, practical way to describe the observable behavior caused by how the engine creates execution contexts and manages lexical environments. The specification instead defines precise algorithms for how declarations like 'FunctionDeclaration', 'VariableStatement', and 'LexicalDeclaration' (let/const) are processed during the creation of these environments.