JavaScript does not just run your code top to bottom. It first scans everything to set up memory, then goes back and executes line by line.
Complete lesson & earn 250 PX
EXERCISE
1Before running any code, JavaScript does a first pass where it allocates memory for all variables and functions. Variables get a placeholder value. Functions get stored completely.
EXERCISE
2Now JavaScript goes through your code a second time, line by line, actually running it. Variables get their real values, and functions create brand new execution contexts when called.
EXERCISE
3Let us trace through a real program step by step and watch both phases work together, from memory creation to final output.
An execution context is created in two passes, and the first one runs before any of your code does. walks the whole scope, finds every declaration, and sets up memory for all of them. No expressions are evaluated. No functions are called. Nothing is printed. The engine is only deciding what names will exist.
This pass is usually called the memory creation phase, and what it puts in each slot depends entirely on how the name was declared.
A var declaration gets undefined. Not the value on the right of the equals sign, which has not been looked at yet, but the placeholder that means this name exists and is currently empty. A function declaration gets something quite different: the whole function, body and all, stored complete and ready to be called.
Two declarations, two very different outcomes:
var x = 7;
function add(a, b) {
return a + b;
}
When the memory phase finishes here, x holds undefined and add holds the entire function. The number 7 has not been assigned to anything yet. It is still waiting in the source.
That difference is directly observable, and it is the whole reason the phase is worth knowing about:
console.log(add(2, 3)); // 5
console.log(x); // undefined
var x = 7;
function add(a, b) {
return a + b;
}
Calling a function above its own declaration works. Reading a variable above its declaration gives you an empty slot instead of an error. Both are consequences of the same pass, treating the two kinds of declaration differently.
One caution before you generalise: a function stored in a variable is a variable, not a function declaration, and it follows the variable rule.
declared(); // "I work"
expressed(); // TypeError: expressed is not a function
function declared() {
console.log("I work");
}
var expressed = function () {
console.log("I do not");
};
expressed exists in memory as undefined, and calling undefined is a TypeError. The shape of the syntax on the right of the equals sign makes no difference at all. What matters is that the name was introduced with var.
Pro Tip: This is the mechanism behind hoisting, the term you will see everywhere. Nothing physically moves to the top of the file. The declarations were simply processed in an earlier pass than the code that reads them, and Unit 2 takes that apart properly.
With memory set up, the engine starts again from the top and actually runs things. This is the code execution phase, and it is where assignments happen, functions are called and output appears. The placeholders put down in the first pass now get replaced with real values, one statement at a time.
For plain assignments this is unremarkable. The engine reaches the line, evaluates the right-hand side, and writes the result into the slot that was reserved earlier.
Placeholders being replaced as execution moves down the file:
var x = 7;
var y = 10;
console.log(x + y); // 17
Before this ran, x and y were both undefined. By the time console.log is reached they hold 7 and 10, because the two assignment statements above it have already been executed.
Function calls are where it gets interesting, because a call does not just run some code. It creates an entirely new execution context, with its own memory phase and its own code phase, nested inside the one that is already running.
function multiply(a, b) {
var answer = a * b;
return answer;
}
var result = multiply(3, 5);
console.log(result); // 15
When multiply(3, 5) is reached, a fresh context is built. Its memory phase puts a, b and answer in place. Its code phase binds a to 3 and b to 5, computes 15 into answer, and hands that value back with return. Then the context is discarded entirely, and everything that lived inside it goes with it.
That last part is not a detail. It is why a function's internals are genuinely private:
function multiply(a, b) {
var answer = a * b;
return answer;
}
multiply(3, 5);
console.log(answer); // ReferenceError: answer is not defined
answer was real, held 15, and did its job. Once multiply returned, its context was torn down and the name stopped existing. The outer context never had a slot called answer, so asking for it now is asking for something that was never there. Every call builds this world fresh and destroys it on return, which is why two calls to the same function never interfere with each other.
Both phases are simple on their own. The way to be sure you have them is to a program where several contexts are created and destroyed, and predict the output before running it.
Read this and decide what it prints before going further:
var x = 1;
a();
b();
console.log(x);
function a() {
var x = 10;
console.log(x);
}
function b() {
var x = 100;
console.log(x);
}
Work through it in order. The global memory phase runs first: x becomes undefined, and a and b are stored complete. The global code phase then starts. Line one assigns 1 to the global x. Line two calls a, which builds its own context, gives its own x the value 10, prints 10, and is destroyed. Line three does the same for b and prints 100. Line four prints the global , which is still , because nothing in either function ever touched it.
The output is 10, 100, 1.
Three separate variables share the name x and never collide, because each one lives in a different execution context. The name is only a label within its own context.
The same idea, with the inner function reading outward instead:
var count = 1;
function show() {
console.log(count);
}
function shadow() {
var count = 99;
console.log(count);
}
show(); // 1
shadow(); // 99
show(); // 1
show has no count of its own, so the lookup continues outward into the global context and finds 1. shadow does have one, so it finds its own and stops there. Calling show again afterwards still prints 1, because shadow never modified the global slot; it created a separate one that has since been destroyed.
Getting this right is what separates guessing from knowing. When you can say which context a name is resolved in, questions about scope stop being memorisation and become something you can work out from first principles.
Pro Tip: Every call gets a fresh context with fresh memory. That is the reason recursion works at all: ten nested calls to the same function are ten separate contexts, each with its own copy of the parameters, none of them aware of the others.
Phase 1 allocates memory for all variables (as undefined) and functions (full code). Phase 2 executes code line by line, assigning real values and running functions.
x1