JS Sketchbooksee JavaScript think ✏️
← back to phase 5

5.3 — The scope chain, precisely

Lesson 3.5 gave you the picture: nested bubbles, a lookup ray shooting outward. It worked. But now you own execution contexts, so you can have the precise version — the one interviewers mean when they say the scope chain.

Here it is: every execution context carries, besides its memory table, one outer link — a rope to another context. Which one? The context of the place the function was written (that's what "lexical scoping" means — decided by text position, fixed forever, regardless of who calls it).

Name lookup is a rope-walk: check the current table, miss, follow the rope, repeat — until found, or the global table misses too and you get a ReferenceError.

And with this one picture, closures stop being magic for good.

watch it happen
const app = "notebook";

function outer() {
  const page = 3;

  function inner() {
    const line = 7;
    console.log(line + " " + page + " " + app);
  }

  inner();
}

outer();

By the time line 8 runs, three execution contexts exist: global (holding app and outer), outer’s (holding page and inner), and inner’s (holding line). Each holds its own names, in its own memory table.

global contextapp: "notebook"outer: ƒouter’s contextpage: 3inner: ƒinner’s contextline: 7ropes =outer linksby the time line 8 runs, three execution contexts exist — global, outer’s,and inner’s — each holding its own namesconsole(nothing yet)
under the hood

The deeper story, with the real names for things — this part is what turns “I saw it” into “I can explain it.”

"Lexical" (or "static") scoping means the ropes are tied by where code is written, not where it's called from. Call inner from anywhere — pass it around, store it in an array, invoke it in another function — its rope still points at outer's context.

This is exactly the property this does NOT have (next lesson's drama: this is decided at call time).

The precise closure story: a returned inner function keeps a reference to its outer context. So the garbage collector (lesson 5.7) cannot sweep that context. Its variables stay alive, reachable only through the rope.

One maker call = one kept context, which is why two counters from the same factory never share a count (3.7's independent counters, explained).

Blocks count too: every { } with a let/const inside gets its own small environment on the chain. That is the machinery behind 3.5's block scope. It explains 5.2's note that var ignores blocks — var registers on the nearest function context, sailing straight past block environments.

your turn

⌨️ a label maker with a long rope

Build a function factory whose product reaches through TWO ropes: its own context, its maker’s context, and the global context — all in one returned string.

requirements:

  • A global const brand = "jfb".
  • A function makeLabel() that declares const sep = " · " and RETURNS an inner function. The inner function takes one parameter id and returns brand + sep + id — one name from each context on the chain.
  • Call the factory once, store the product as label, and print label("cart"). (Note that makeLabel has RETURNED by then — and the rope still holds. That’s 3.7’s closure, now with its precise machinery.)

when you press RUN, the console must show exactly:

jfb · cart

✏️ Quick check 1

Type exactly what this prints:

const size = "L";
function shop() {
  function tag() {
    console.log(size);
  }
  tag();
}
shop();

✏️ Quick check 2

This code throws when the last line runs. Type the ERROR NAME:

function bake() {
  const temp = 200;
}
bake();
console.log(temp);

✏️ Quick check 3

The rope (outer link) is decided by where a function is ___ — type: written or called.

teach it back

🗣️ Now teach it back

Explain the scope chain precisely to a friend: what each execution context carries, how a name lookup actually proceeds, what finally causes a ReferenceError — and use it to explain why closures work.

Write it as if your friend is sitting next to you. Saved to your journal — future-you will use these notes to teach others.

a few sentences, minimum — you’ve got this
to remember
Each execution context = a memory table + ONE outer link (rope) tied to where its function was WRITTEN — lexical, fixed forever.
Lookup: current table → rope → table → rope… first hit wins (shadowing); global miss → ReferenceError. Outward only — privacy by direction.
A closure is a kept-alive context at the end of a returned function’s rope — one maker call, one private context.
next: 5.4 this: the compass →