Skip to content
Lesson 1 of 15

Step 1 of 5 · Reading · ~4 min

Learn

Scope and Closures

Block Scope and Hoisting

JavaScript Fundamentals used let and const inside blocks without asking where a name stops being visible. This lesson answers that exactly: which names a line of code can see, why var breaks the rules, and what "hoisting" really is. By the end you can predict what a snippet prints without running it.

Scopes nest, and lookup walks outward

A scope is the region of code where a name can be used. A function body is a scope, and so is every { ... } block (an if, a for, or a bare pair of braces). Scopes sit inside each other. When the engine meets a name it looks in the current scope, then the one around it, then the next, up to the top level. This is the scope chain.

javascript

let and const stop at the block, var does not

let and const belong to the nearest { }. var belongs to the nearest function (or the whole file) and ignores blocks completely:

javascript

With let i the return i line would throw a ReferenceError, because that i ceased to exist at the closing brace of the loop. That leak is why modern code avoids var.

Shadowing

A declaration with the same name as an outer one shadows it: inside that scope the inner name wins, and the outer variable is untouched.

javascript

Declaring the same let name twice in one scope is a SyntaxError, and because it is found before any code runs, the whole file refuses to start.

Hoisting: declarations exist before their line runs

When a scope is entered, the engine first creates every binding declared in it, and only then runs the code. That is hoisting. What differs is the state each kind of binding starts in:

DeclarationBefore its own line
function f() {}fully usable, the body is already attached
var x = 1exists, holds undefined
let, const, class (classes come later in this course)exists but uninitialised: touching it throws ReferenceError
javascript

The span between entering the scope and reaching the let line is the temporal dead zone (TDZ). That let is hoisted too, and this is how you can prove it:

javascript

If the inner mode did not exist yet, the lookup would reach the outer "dark". It throws because the inner one already exists, shadows the outer, and is still uninitialised. Even typeof is not safe in the TDZ: typeof neverDeclared gives "undefined", but typeof on a let before its line throws.

A function stored in a variable follows the variable's rule, not the declaration's. var greet = function () {} called one line early fails with TypeError: greet is not a function, because greet is undefined at that moment.

Common mistakes

  • Reading a loop's var counter after the loop and trusting it. It leaks, and so does any var set inside an if.
  • Expecting console.log(x) before var x = 5 to throw. It prints undefined, which hides the ordering bug.
  • Expecting the same before let x = 5. That one throws.
  • Declaring a let twice in one scope, for example after pasting code. The file fails before printing anything.

Your exercise

You will build the thing this lesson describes: a scope chain. The starter runs a tiny script of scope commands and asks your three scope functions the questions. The usual slip is var, which must skip past block scopes to the nearest function scope.

Up nextClosuresScope and Closures

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…