Scope, Hoisting & TDZ
Key Learning Objectives
Differentiate between Lexical Environment creation phase and execution phase in V8.
Understand the Temporal Dead Zone (TDZ) and why accessing let/const before declaration throws ReferenceError.
Contrast function declaration hoisting with function expressions assigned to var or let.
Master variable shadowing and block scope rules.
The Interview Problem
What is printed to the console when the following snippet executes, and why does bar() behave differently than foo()?
1console.log(typeof nonExistent);2console.log(typeof hoistedVar);34var hoistedVar = 10;56function testScope() {7 console.log(hoistedVar);8 var hoistedVar = 20;9 console.log(hoistedVar);10}1112testScope();
Predict Console Output
Select the option that matches what standard ECMAScript prints to the console:
'undefined' 'undefined' undefined 20
'undefined' 'number' 10 20
ReferenceError ReferenceError 10 20
'undefined' 'undefined' 10 20
V8 Engine Execution Trace
Step 1 of 8 (Line 1)Creation phase: V8 registers hoistedVar initialized to undefined. testScope function declaration is hoisted and fully initialized.
Deep Technical Breakdown
Hoisting & Environment Record Lifecycle
When a JavaScript engine executes a script or function, it operates in two distinct phases:
- Creation Phase:
- Scans code for declarations.
vardeclarations are hoisted to function/global scope and initialized withundefined.- Function declarations (
function foo() {}) are hoisted and fully defined in memory. letandconstdeclarations are hoisted to block scope but left uninitialized (entering the Temporal Dead Zone).
- Execution Phase:
- Runs line-by-line statements, evaluating expressions and performing assignments.
- Shadowing Mechanics:
- In
testScope(), the declarationvar hoistedVar = 20;is parsed during the creation phase oftestScope. It shadows the outerhoistedVar = 10for the entire duration of the function body. Before line 8 executes, localhoistedVarholdsundefined.
- In
Common Traps & Mistakes
Assuming `let` and `const` are not hoisted. They ARE hoisted to the start of their block scope, but remain uninitialized until execution reaches their declaration line.
Assuming `typeof` is always safe. Accessing a `let` variable in its TDZ with `typeof` throws a `ReferenceError`, breaking the historical safety of `typeof`.
Using function expressions (`var fn = () => {}`) expecting them to be callable before declaration.
FAANG Follow-Up Probes
Probe #1
Why does accessing a `let` variable before declaration throw a `ReferenceError` while `var` returns `undefined`?
Probe #2
How does the Temporal Dead Zone apply to default function parameters, e.g. `function f(a = b, b = 2) {}`?
Probe #3
How do ES modules handle circular dependencies through live bindings and hoisting?
