Scope & Hoisting
var/let/const, hoisting, the temporal dead zone, lexical and block scope, execution context, and variable shadowing.
12 questions
JuniorTheoryVery commonWhat is hoisting in JavaScript?
What is hoisting in JavaScript?
Hoisting is the engine registering declarations before running code, so a name exists in its scope from the top. A var is hoisted and set to undefined, so reading it early yields undefined. A function declaration is hoisted whole, callable before its line; let/const are hoisted but stay uninitialized.
Common mistakes
- ✗Believing hoisting physically moves code rather than registering names early
- ✗Expecting a
let/constread before its line to giveundefinedlikevar - ✗Thinking function declarations are not callable before their line
Follow-up questions
- →Why is a function declaration callable before its line but a function expression is not?
- →What does a
varread before its assignment evaluate to, and why?
JuniorTheoryVery commonWhat are the differences between var, let, and const?
What are the differences between var, let, and const?
var is function-scoped, hoisted and initialized to undefined, and may be redeclared. let and const are block-scoped and hoisted but left uninitialized until their line (a temporal dead zone before that), and cannot be redeclared in a scope. const also forbids reassigning the binding.
Common mistakes
- ✗Thinking
constmakes the value immutable rather than just blocking rebinding - ✗Believing
varrespects block scope likelet - ✗Assuming
let/constare not hoisted at all (they are, but stay in the TDZ)
Follow-up questions
- →Why does redeclaring
letin twoswitchcases throw, but in separate blocks it does not? - →What does
constactually freeze — the binding or the value?
JuniorTheoryCommonWhat is block scope, and which declarations respect it?
What is block scope, and which declarations respect it?
A block is any {} pair — an if, a for, or bare braces. Block scope means a variable is visible only inside the nearest enclosing block. let and const are block-scoped, so they vanish outside the block. var ignores blocks entirely and leaks to the whole enclosing function.
Common mistakes
- ✗Thinking
varis block-scoped likelet/const - ✗Assuming
if/forbraces do not create a scope forlet/const - ✗Expecting a
letfrom inside a loop to survive after the loop
Follow-up questions
- →Why does a
letin aforheader create a fresh binding each iteration? - →How does block scope fix the classic
var-in-a-loop closure bug?
JuniorTheoryCommonWhat is function scope, and how does var use it?
What is function scope, and how does var use it?
Function scope means a variable is visible everywhere inside the function that declares it, regardless of nested blocks. var is function-scoped: a var declared inside any if or loop is reachable throughout the whole function. Each function call creates a fresh scope for its local variables.
Common mistakes
- ✗Thinking each call reuses one shared scope instead of a fresh one
- ✗Believing
letis function-scoped likevar - ✗Expecting a
varinside a block to be limited to that block
Follow-up questions
- →How does a fresh scope per call make recursion and closures possible?
- →Why can two nested functions both declare
var xwithout colliding?
MiddleTheoryCommonWhat is lexical scope, and how does the scope chain resolve a name?
What is lexical scope, and how does the scope chain resolve a name?
Lexical scope means a name's scope is fixed by where the code is written, not where it runs. A nested function can read variables of its enclosing functions. To resolve a name, the engine walks the scope chain outward — local, then each enclosing scope, then global — stopping at the first match.
Common mistakes
- ✗Confusing lexical scope with dynamic (call-site) scope
- ✗Thinking the scope chain is searched from global inward rather than local outward
- ✗Believing a function can only reach its immediate parent scope
Follow-up questions
- →How do closures rely on lexical scope to capture variables?
- →Why does shadowing stop the scope-chain search at the inner binding?
MiddleTheoryCommonWhat is the Temporal Dead Zone in JavaScript?
What is the Temporal Dead Zone in JavaScript?
The temporal dead zone is the span between entering a scope and the line that initializes a let or const. The name is hoisted and exists in scope, but reading or writing it during the TDZ throws a ReferenceError. var has no TDZ — it reads as undefined before its line instead.
Common mistakes
- ✗Thinking
varhas a TDZ (it reads asundefinedinstead) - ✗Believing the name does not exist in scope during the TDZ
- ✗Expecting a TDZ access to return
undefinedrather than throw
Follow-up questions
- →Why is
typeof xaReferenceErrorfor aletin its TDZ but safe for an undeclared name? - →How does the TDZ help catch use-before-declare bugs?
JuniorTheoryOccasionalWhat are global variables, and how is one created accidentally?
What are global variables, and how is one created accidentally?
A global variable lives in the top-level scope and is reachable from anywhere in the program. Declaring at the top level makes one deliberately; assigning to a name without var/let/const creates one accidentally — but only in non-strict mode, where strict mode throws a ReferenceError instead.
Common mistakes
- ✗Assuming an undeclared assignment creates a global even in strict mode
- ✗Thinking
varinside a function leaks to the global scope - ✗Believing globals are harmless rather than a source of name collisions
Follow-up questions
- →Why does strict mode turn an accidental global into a
ReferenceError? - →What problems do shared global variables cause in a large codebase?
MiddleTheoryOccasionalWhat is an execution context, and what are its two phases?
What is an execution context, and what are its two phases?
An execution context is the environment a piece of code runs in, holding its variables, scope chain, and this. It runs in two phases: the creation phase sets up bindings and hoists declarations; the execution phase runs the code line by line. There is one global context plus one per function call.
Common mistakes
- ✗Thinking there is only one execution context for the whole program
- ✗Reversing the order of the creation and execution phases
- ✗Conflating the execution context with the call stack
Follow-up questions
- →How does the creation phase explain why
varreads asundefinedearly? - →What gets pushed and popped on the call stack as contexts come and go?
MiddleTheoryOccasionalWhat are variable shadowing and illegal shadowing?
What are variable shadowing and illegal shadowing?
Shadowing is an inner-scope variable reusing an outer name; inside the inner scope the inner binding wins and the outer is hidden. Illegal shadowing is a SyntaxError when a var collides with a let/const in an overlapping scope, because var leaks out of the block and clashes with the block-scoped name.
Common mistakes
- ✗Thinking ordinary shadowing is itself an error
- ✗Believing the outer binding wins inside the inner scope
- ✗Reversing the illegal case — it is
varoverlet/const, notletovervar
Follow-up questions
- →Why does
let a; { var a; }throw butvar a; { let a; }does not? - →How does shadowing interact with the scope-chain lookup?
MiddleTheoryOccasionalWhat is the difference between an undeclared and an undefined variable?
What is the difference between an undeclared and an undefined variable?
An undefined variable is declared but unassigned, so reading it returns undefined. An undeclared variable was never declared anywhere in the scope chain, so reading it throws a ReferenceError (x is not defined). The first is a normal value; the second is a name-lookup failure.
Common mistakes
- ✗Thinking reading an undeclared variable returns
undefinedrather than throwing - ✗Assuming both cases throw the same error
- ✗Confusing the value
undefinedwith a failed name lookup
Follow-up questions
- →Why does
typeof undeclaredVarreturnundefinedinstead of throwing? - →How does hoisting explain why an early
varread givesundefined?
SeniorTheoryRareHow does the creation phase set up hoisting, the TDZ, and the scope chain?
How does the creation phase set up hoisting, the TDZ, and the scope chain?
In the creation phase the engine builds the context's environment record: it binds each var to undefined, binds function declarations to their function, and binds let/const names but leaves them uninitialized — the source of the TDZ. It also links the outer environment, forming the scope chain. The execution phase then assigns the real values.
Common mistakes
- ✗Thinking the creation phase assigns final values rather than placeholders
- ✗Believing the scope chain is built at call time rather than from lexical nesting
- ✗Treating the TDZ as a teaching metaphor instead of an uninitialized binding
Follow-up questions
- →Why are function declarations usable before their line but function expressions are not?
- →How does the environment record differ between the global and a function context?
SeniorTheoryRareWhy is JavaScript lexically scoped, and how does that resolve shadowed names?
Why is JavaScript lexically scoped, and how does that resolve shadowed names?
JavaScript is lexically scoped: a function's free variables resolve against the scopes that enclose it in the source, fixed when the function is defined, not when it is called. A name is looked up outward through that lexical chain, and the first (innermost) binding found shadows any same-named outer one. This is also what lets a closure keep its defining scope alive.
Common mistakes
- ✗Confusing JavaScript's lexical scoping with dynamic (caller-based) scoping
- ✗Thinking the lookup starts at global and descends rather than local and ascends
- ✗Believing closures capture the global scope rather than the defining one
Follow-up questions
- →How would dynamic scoping change which variable a closure captures?
- →Why can two closures from the same factory share or isolate state?