5.1 — Execution contexts: the two passes
Here's a small impossibility: line 1 of today's code calls a function that isn't written until line 3 — and it works. If code truly ran top-to-bottom, line 1 should crash with "sayHi is not defined." It doesn't. Which means the story you've had since lesson 0.3 — code runs top to bottom — is missing a chapter.
The missing chapter: the engine reads your code TWICE. Pass one — the creation phase — sweeps the scope and registers every declaration into a memory table. Pass two — the execution phase — actually runs the lines.
The table plus the code being run is called an execution context, and it is THE concept this whole phase is built on: hoisting, scope chain, closures-done-precisely, even this all live here.
sayHi();
function sayHi() {
console.log("hi!");
}
console.log(score);
var score = 10;
console.log(score);Before executing a single line, the engine builds ONE EXECUTION CONTEXT for this scope: a memory table for its names, plus the code to run. Every scope — global, every function call — gets one.
The deeper story, with the real names for things — this part is what turns “I saw it” into “I can explain it.”
Precision matters here: nothing physically moves in your file. "Hoisting" describes the effect of the creation phase — names are registered before any line runs, so they behave as if declared at the top.
What differs per declaration kind (var / let / const / function) is how they're registered — that's the entire next lesson.
The execution context is the phase's master picture. Each holds its memory table, its outer-scope reference (lesson 5.3's chain), and — for function calls — its own this (lesson 5.4).
The call stack from 3.6 is, precisely, a stack of execution contexts. One concept, four lessons of payoff.
Fun fact: you use two-pass reading too. Skim a recipe before cooking — "ah, there's an oven step, a marinade, a sauce" — then execute it step by step. A cook who starts frying at word one discovers "marinate overnight" too late; the skim is your creation phase.
⌨️ prove the two passes
Write a program that could only work because of pass 1 — a function called before its definition, and a var read before its assignment. You’re not fixing this behavior; you’re demonstrating you can predict it.
requirements:
- First line of your program: print
banner— avarthat is declared further down. It must printundefined(and you must know why). - Then call
setBanner()— a function DECLARATION written at the very bottom of the file — which assigns"Grand Opening!"tobanner. - Print
banneragain, then place thevardeclaration and the function declaration at the bottom.
when you press RUN, the console must show exactly:
✏️ Quick check 1
Type exactly what this prints:
greet();
function greet() {
console.log("yo");
}✏️ Quick check 2
Type exactly what this prints:
console.log(x); var x = 5;
✏️ Quick check 3
The engine’s first sweep — where declarations are registered before anything runs — is called the ___ phase. Type the word.
🗣️ Now teach it back
A friend is baffled that calling a function above its definition works, but reading a var above its line gives undefined. Explain the two passes — and why the two cases behave differently.
Write it as if your friend is sitting next to you. Saved to your journal — future-you will use these notes to teach others.