The Memory (Creation) Phase
Introduction
Before JavaScript executes your first line, it performs a memory phase for the current execution context. The engine scans declarations, creates bindings, and decides their initial state. This is where the behaviors called hoisting and temporal dead zone are born.
The key is precise: declarations are prepared early, but assignments and most expressions still wait for the execution phase.
Why This Matters
Most output-prediction bugs are creation-phase bugs. If you can build the memory table before executing a snippet, you can predict undefined, ReferenceError, TypeError, callable function declarations, and shadowing behavior without guessing.
Theory
What the engine prepares
During creation, JavaScript builds an environment record for the context. It does not run business logic yet; it prepares names.
| Declaration form | Creation-phase state | What happens when read before the line? |
|---|---|---|
var x | Binding exists and is initialized to undefined | Returns undefined. |
function f() {} | Binding exists and points to the function object | Callable before its declaration line. |
let x | Binding exists but is uninitialized | Throws ReferenceError due to TDZ. |
const x | Binding exists but is uninitialized | Throws ReferenceError due to TDZ. |
class C {} | Binding exists but is uninitialized | Throws ReferenceError due to TDZ. |
var f = function () {} | f exists as undefined; function value assigned later | Calling before assignment throws TypeError. |
Parameters are initialized too
When a function context is created, parameter bindings are initialized before the body runs. Default parameters are evaluated left to right, which creates its own TDZ-like traps: an earlier default cannot read a later parameter.
Function declarations are special
A function declaration is available as a function object during creation. That is why sayHi() can work before the declaration appears in source. A function expression assigned to var does not get this treatment; only the variable binding is prepared.
let and const are hoisted, but not usable
A common incorrect answer is that let and const are not hoisted. They are hoisted in the sense that the binding is created at scope entry. They are just not initialized until execution reaches the declaration. The interval before that line is the temporal dead zone.
Mental workflow for interviews
- Draw a memory table for the current scope.
- Fill
varwithundefined. - Fill function declarations with function objects.
- Mark
let,const, andclassas TDZ. - Only then execute statements top to bottom.
Visual Diagrams
Code
console.log(a)
var a = 1
let b = 2
function run() {}
Creation phase
a -> undefined
b -> uninitialized TDZ
run -> function object
Execution phase later performs the assignments a = 1 and b = 2The declaration kind determines the initial binding state.
Creation phase greet -> undefined Execution phase greet() before assignment -> TypeError greet = function object greet() after assignment -> works
The variable is hoisted; the function expression value is not.
Code Examples
Build the memory table first
Before execution, count exists as undefined, show is callable, and name is present but unavailable in the TDZ.
Function declaration vs function expression
Only the declaration is initialized to a function object during creation. The expression assignment happens later.
Playground
Press Run to execute the code and see output here.
Output Prediction
Predict the output #1
1console.log(score);2var score = 10;3console.log(score);Predict the output #2
1sayHi();2 3try {4 greet();5} catch (error) {6 console.log(error.name);7}8 9function sayHi() {10 console.log('hi');11}12 13var greet = function () {14 console.log('hello');15};16 17greet();Predict the output #3
1try {2 console.log(name);3} catch (error) {4 console.log(error.name);5}6 7let name = 'Ada';8console.log(name);Coding Exercises
Classify declaration creation states
MediumImplement describeCreationPhase(declarations). Each item has { name, kind }, where kind is 'var', 'function', 'let', 'const', or 'class'. Return strings describing the binding's creation-phase state, such as 'count -> undefined' or 'User -> TDZ'.
Interview Questions
1What happens in the creation phase of an execution context?
The engine creates the environment record for the scope. It registers declarations, initializes var bindings to undefined, stores function declarations as function objects, creates let/const/class bindings in an uninitialized TDZ state, sets up the scope chain, and establishes the this binding. It does not execute assignments or function expressions yet.
Follow-ups
- Why can function declarations be called before their source line?
- Are let and const hoisted?
2Why does calling a var-assigned function expression before its line throw TypeError instead of ReferenceError?
Because the var binding exists and is initialized to undefined during creation, so the name resolves successfully. The error happens because execution tries to call the current value, undefined, as a function. That is a TypeError. A missing name would be ReferenceError; a TDZ name would also be ReferenceError.
Quiz
1. What is the creation-phase state of `var total`?
2. Which declaration is callable before its source line executes?
Summary
- The memory phase creates bindings before statements run.
- `var` starts as `undefined`; function declarations start as function objects.
- `let`, `const`, and `class` bindings exist but are uninitialized in the TDZ.
- Function expressions, arrow functions assigned to variables, and assignments happen during execution, not creation.
Cheat Sheet
Creation phase table:
var -> undefined
function declaration -> function object
let / const / class -> TDZ until declaration executes
var f = function () {} -> f is undefined; function value assigned later
Interview process: make the memory table first, then execute line by line.