JavaScript course menu
JavaScript Scope and Hoisting: Global, Function and Block Scope, TDZ
Understand scope in JavaScript: global, module, function and block scope, the scope chain and shadowing, hoisting of var, let, const and function declarations, the temporal dead zone, and why to avoid global variables.
On this page
Scope decides where a variable can be used. A variable declared inside a function or block is not visible outside it. Understanding scope prevents a whole class of bugs and is the foundation for closures.
Block scope#
let and const are block-scoped: they exist only inside the nearest { }:
const discount = 10;
if (discount > 5) {
const message = "Big discount!";
console.log(message);
}
console.log(typeof message);Big discount!
undefinedmessage does not exist outside the if block. Loop variables declared with let are scoped to the loop.
Function scope#
Variables declared inside a function are local to that function:
function calculate() {
const result = 6 * 7;
return result;
}
console.log(calculate());
try {
console.log(result);
} catch (error) {
console.log(error.message);
}42
result is not definedThe scope chain#
Inner code can read variables from outer scopes, but not the other way around. When JavaScript looks up a name, it searches the current scope, then the enclosing scope, and so on outwards to the global scope:
const siteName = "Laviyx"; // outer (module) scope
function outer() {
const section = "JavaScript"; // outer function scope
function inner() {
const lesson = "Scope"; // inner function scope
console.log(`${siteName} > ${section} > ${lesson}`);
}
inner();
}
outer();Laviyx > JavaScript > ScopeThis is called lexical scope: what a function can see depends on where it is written in the code, not where it is called from.
Shadowing#
An inner variable with the same name shadows (hides) the outer one inside its scope:
const level = "global";
function show() {
const level = "function";
console.log(level);
}
show();
console.log(level);function
globalShadowing is legal but can be confusing; prefer distinct names.
Global scope#
Variables declared at the top level of a classic script are global: visible everywhere, and easy to overwrite by accident from another script. Modules (files using import/export, and the scripts on this site) have their own module scope instead, so top-level variables stay private to the file. Keep globals to a minimum.
Hoisting#
Before running code, JavaScript sets up all declarations in each scope. This is called hoisting, and it behaves differently for each kind of declaration.
Function declarations#
Function declarations are hoisted completely, so you can call them before they appear:
console.log(double(21));
function double(n) {
return n * 2;
}42var#
var declarations are hoisted and initialised to undefined, which hides mistakes:
console.log(count);
var count = 5;
console.log(count);undefined
5let, const and the temporal dead zone#
let and const are also hoisted, but not initialised. Using them before their declaration line throws an error. The time between the start of the scope and the declaration is called the temporal dead zone (TDZ):
try {
console.log(total);
let total = 10;
} catch (error) {
console.log(error.name + ": " + error.message);
}ReferenceError: Cannot access 'total' before initializationAn error is much better than a silent undefined, which is another reason to use let and const rather than var.
Loops and scope#
Because let creates a new binding for each loop iteration, callbacks created in a loop each see their own value:
const callbacks = [];
for (let i = 1; i <= 3; i++) {
callbacks.push(() => i);
}
console.log(callbacks.map((fn) => fn()));[ 1, 2, 3 ]With var i, all three callbacks would share one variable and return 4. This classic interview question is explained in Closures.
Practice#
Practice exercise
Predict the output, then check:
let x = 1;
function test() {
let x = 2;
if (true) {
let x = 3;
console.log("block", x);
}
console.log("function", x);
}
test();
console.log("outer", x);Show solution
let x = 1;
function test() {
let x = 2;
if (true) {
let x = 3;
console.log("block", x);
}
console.log("function", x);
}
test();
console.log("outer", x);block 3
function 2
outer 1Each let x creates a new variable in its own scope that shadows the outer ones.
Key takeaway#
Finished this lesson?