Q1What happens if you display unit on the line after if (true) { const unit = "yen"; }?
Scope and Hoisting — Where Variables Can Be Read, and When
Scope decides where a variable can be read, and hoisting registers declarations before code runs. Covers shadowing, the TDZ, and why to skip var.
If you use a value calculated inside curly braces from outside them, execution stops. It also stops when a line reads a setting that's declared further down. Both errors come down to where a name can be used, and from what point.
This article covers scope, which decides where a declared name can be read, and hoisting, which registers declarations before the code runs.
Variables That Only Work Inside Braces — Block Scope
Say you want the cart screen to show how much more the customer needs to spend to get free shipping. You can calculate the shortfall inside the if that checks for free shipping, but if you try to display that value outside the braces, execution stops with a ReferenceError.
Scope (the range that decides where a name can be read) is marked off by curly braces. The range of one pair of braces is called block scope, and a variable declared with const or let can only be read inside the innermost block scope that encloses its declaration.
const cartTotal = 4800; // Outermost level, not inside any braces
function checkShipping(total) {
const freeLine = 5000; // Only inside checkShipping's braces
if (total < freeLine) {
const shortage = freeLine - total; // Only inside the if's braces
console.log(`${shortage} yen to go`); // 200 yen to go
}
// Variables declared outside can be read from inside
console.log(cartTotal); // 4800
// console.log(shortage); // ReferenceError: shortage is not defined
}
checkShipping(cartTotal);
const cartTotal = 4800— readable from anywhere in the filecheckShipping(cartTotal)— calls the function from the outermost level
const freeLine = 5000— readable only inside these braces- The outer
cartTotalis readable here too console.log(shortage)— not readable once you're outside the if, soReferenceError
const shortage = 200— readable only inside these braces- Both
freeLineandcartTotalcan be read
if can't be read once you leave its braces. Reading only works one way: from inside to outside.The outermost level, not inside any braces, is the global scope, and variables there can be read from anywhere. A function body is a pair of braces too, so you can't read freeLine from outside the function. The range marked off by a function's braces is called function scope.
The Inner Declaration Wins — Shadowing
When you add a function that calculates prices at a reduced tax rate, you might declare a variable inside it called taxRate, the same name as the outer one. If you don't know which value is used, you can't track down why a result came out wrong.
If you declare a name in an inner scope that already exists in an outer one, the inner declaration is used inside those braces. This is called shadowing (an inner declaration hiding an outer variable with the same name), and it happens with both if braces and function braces.
const taxRate = 0.1; // Tax rate set on the outside
function withTax(price) {
return Math.floor(price * (1 + taxRate));
}
function withReducedTax(price) {
const taxRate = 0.08; // Same name, declared inside the function
return Math.floor(price * (1 + taxRate));
}
console.log(withTax(1000)); // 1100
console.log(withReducedTax(1000)); // 1080
console.log(taxRate); // 0.1 (the outer one hasn't changed)
taxRate finds a different declaration depending on where you read it. An inner declaration doesn't overwrite the outer value.A name is looked up starting from the innermost braces around the line that reads it. Inside withReducedTax, the search stops as soon as it finds 0.08, so the outer 0.1 is never read. Code where one name means a different value in each function is hard to follow, so in your own code, give them distinct names such as reducedTaxRate.
Without const, You Assign to the Outer Variable
If you write taxRate = 0.08; inside a function without const, no new variable is created — it's an assignment to the outer taxRate. If the outer one is a const, execution stops with a TypeError, but if it's a let, the value gets overwritten, and even a later call to withTax(1000) returns 1080.
Unreadable Until the Declaration Line — Hoisting and the TDZ
Say you want to group settings like a point rate or a shipping fee on lines below the code that uses them. As you saw in the functions article, a function declaration can be called even when it's written further down, but reading a let or const setting before its declaration line stops execution with a ReferenceError.
Before JavaScript starts running a scope, it registers the names of the declarations in it. This is called hoisting (declarations being registered before the code runs). A function declaration registers its name and body together, so you can call it from lines above the declaration. let and const register only the name; the value goes in when the declaration line runs.
// The function body reads pointRate when it's called
function calcPoint(amount) {
return Math.floor(amount * pointRate);
}
// Calling it here means the pointRate declaration line hasn't run yet
// console.log(calcPoint(3200));
// → ReferenceError: Cannot access 'pointRate' before initialization
let pointRate = 0.05; // Settings grouped further down
console.log(calcPoint(3200)); // 160 (readable, since the declaration line has run)
let line runs, the value isn't there, even when a function written above reads it. Whether you can read it depends on the order things run, not on where you wrote them.The stretch from entering a scope until the let declaration line runs is called the TDZ (Temporal Dead Zone — the period when a name can't be read until its declaration line runs). If the error message says is not defined, the name isn't in scope at all. If it says before initialization, the name exists, but you read it before its declaration line ran.
Registered Early as undefined — Why Not to Use var
You'll see variables declared with var in older articles and libraries, and if you copy code that works, var can come along with it. But var differs from const and let in two ways: where the variable is valid, and what you get when you read it before its declaration.
A var declaration is registered early too, but undefined goes in at the same time. So referencing it on a line above the declaration doesn't cause an error — execution just moves on with undefined. It isn't limited to the braces, either: it's valid throughout the enclosing function.
function checkStock(orderCount) {
// Reading it before the declaration doesn't stop anything; it holds undefined
console.log(stock); // undefined
var stock = 12;
console.log(stock); // 12
if (orderCount <= stock) {
var status = "Ready to ship"; // Declared with var
let note = "Ship from warehouse A"; // Declared with let
}
// var is valid throughout the function, so it's readable outside the if
console.log(status); // Ready to ship
// console.log(note); // ReferenceError: note is not defined
}
checkStock(3);
if, a var value can be read on lines after the braces end. With let, execution stops on the line that reads it outside its range.A function has only one variable per var name, so declaring the same name again with var inside an if doesn't shadow anything — it overwrites the same variable. The table below shows, for each kind of declaration, what's stored when the name is registered before the code runs, and what you get when you read it above the declaration line.
| Declaration | Registered Before Running | Read Above the Declaration |
|---|---|---|
| var stock = 12 | The name and undefined | Continues with undefined |
| let stock = 12 | The name only (no value) | Stops with ReferenceError |
| const stock = 12 | The name only (no value) | Stops with ReferenceError |
Switching var to let Makes Some Lines Stop
When you replace var with let in old code, lines that read the variable before its declaration, or outside the if, stop with a ReferenceError. Those lines were reading undefined, or a value from other braces, all along. To fix them, move the declaration above the line that reads it, into braces that also enclose that line.
Knowledge Check
Answer each question one by one.
Q2Near the top of the file, you define a function show that returns rate. You then call it on a line below let rate = 0.1;. What does it return?
Q3If you write console.log(count); on a line above var count = 3;, what is displayed?