The short answerWrite const by default and let when the value must change; leave var to old code. let and const live in their block, the nearest pair of braces { }, and cannot be used before their line has run. var ignores blocks and starts as undefined (no value yet): it hides mistakes that let and const report.
The film
Film coming soon
const, let, and not var
const TAX = 0.2; // never gets a new value
let total = 0; // changes as items are added
total = total + 10;
constby default. GiveTAXa new value and the program stops with aTypeError.letwhen the value must change, like a running total.varis the old way, from before 2015. Do not write it, but recognise it: old tutorials and some AI answers still use it.
Careful const locks the name, not the contents. A const array can still grow with push.
Where a variable lives
Where a variable lives is its scope. A block is the code between { and }: a function, an if, a loop. let and const live in the block where they are declared. var ignores the blocks of if and loops: it lives in the whole function.
- Inside the loop. With
let,ilives in the loop; withvar,ilives in the whole function. Line 4 usestotalfrom the box around it: from inside, you see out. - After the loop.
console.log(i)withlet:ReferenceError: i is not defined, and the program stops. Withvar:2, and the program goes on, no warning. - Before its line.
console.log(total)abovelet total = 0;:ReferenceError: Cannot access 'total' before initialization, and the program stops (consttoo). Abovevar total = 0;:undefined, and the program goes on, no warning.
let and const report the mistake; var hides it.
From inside a block you see out; from outside you cannot see in.
Before its line
Chapter 01 showed that the engine reads the whole file before running it. So it already knows every name, even above its declaration:
console.log(a); // undefined
var a = 1;
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 2;
var gives undefined, JavaScript’s “no value yet”, and carries on with no warning. let and const stop the program with a clear error. A function declaration is ready from the first line, so you can call it above its line; a function stored in a const, like const add = (a, b) => a + b, is not. The engine knowing every name before it runs is called hoisting (to hoist: to lift).
Try it
An assistant will type most of your code. Predict it, write it once yourself, test it. If you cannot explain a line, it is not yours.
const locks the name
const TAX = 0.2;
const cart = [];
cart.push(10);
cart.push(5);
console.log(cart);
TAX = 0.25;
console.log(TAX);
Predict the two lines it prints, then Run.
The Node.js Playground: desktop Chrome or Edge.
Check after you predict
Web Playground · Console panel[10, 5] Uncaught TypeError: Assignment to constant variable. (line 6)
Node.js Playground · Console tabStarting Node.js… (the first run takes a few seconds) ▶ node index.js [ 10, 5 ] TypeError: Assignment to constant variable. ✗ Crashed (code 1) — the error is above
push changes what is inside the array: allowed. TAX = 0.25 gives the name a new value: TypeError. The program stops on that line, so the last console.log never runs.
Firefox and Safari word these errors differently (their own engine, chapter 01); the error type, TypeError or ReferenceError, is the same.
Before its line
console.log(add(10, 20));
console.log(discount);
var discount = 5;
console.log(TAX);
const TAX = 0.2;
function add(a, b) {
return a + b;
}
Predict the three lines, then Run.
Check after you predict
Web Playground · Console panel30 undefined Uncaught ReferenceError: Cannot access 'TAX' before initialization (line 4)
Node.js Playground · Console tabStarting Node.js… (the first run takes a few seconds) ▶ node index.js 30 undefined ReferenceError: Cannot access 'TAX' before initialization ✗ Crashed (code 1) — the error is above
add is a function declaration: ready from the first line. discount is a var: it exists, but holds undefined until line 3. TAX is a const: it cannot be used before line 5.
Fix the var bug
function cartTotal(prices) {
var sum = 0;
for (var i = 0; i < prices.length; i++) {
var price = prices[i];
sum = sum + price;
}
return price;
}
console.log(cartTotal([10, 20, 30]));
- Run it. The cart is 10 + 20 + 30: you expect 60. What prints?
- Replace every
varwithletand run again. Read the error: which line? - Fix that line so it prints 60.
Check after you try
Step 1: 30, and no error (the Node.js Playground ends with ✓ Finished (code 0)).
Step 2 (sum and i with let, price with let or const):
Web Playground · Console panelUncaught ReferenceError: price is not defined (line 7)
Node.js Playground · Console tabindex.js:7 return price; ^ ReferenceError: price is not defined at cartTotal (index.js:7:3) at Object.<anonymous> (index.js:10:13) at Module._compile (node:internal/modules/cjs/loader:…) … ✗ Crashed (code 1) — the error is above
Step 3: return sum; prints 60.
The function returned price, the last item, not sum. With var, price survived the loop, so the bug gave a wrong number and no error. With let, the engine pointed at line 7. Each pass of the loop makes a new price and none is given a new value, so const works too.
Reading the error. Read a Node error from the top: the file and line (index.js:7), the code with a ^ under the spot, then the error. In the at … lines, the ones with index.js are your code (at cartTotal, line 7, called from line 10); the node:internal ones are Node’s own: skip them. The top part is what you paste to an assistant.
Write it yourself
const prices = [12, 8, 30];
// 1. a constant TAX equal to 0.2
// 2. a variable total that starts at 0
// 3. a for loop with a counter i that adds each price to total
// 4. print total * (1 + TAX)
- Follow the four comments, then Run: you should get 60.
- Add
console.log(i)after the loop. Which error, and why?
Check after you try
One possible answer:
const prices = [12, 8, 30];
const TAX = 0.2;
let total = 0;
for (let i = 0; i < prices.length; i++) {
total = total + prices[i];
}
console.log(total * (1 + TAX));
console.log(i);
Web Playground · Console panel60 Uncaught ReferenceError: i is not defined (line 9)
Node.js Playground · Console tab▶ node index.js 60 index.js:9 console.log(i); ReferenceError: i is not defined ✗ Crashed (code 1) — the error is above
12 + 8 + 30 = 50, and 50 × 1.2 = 60. i lived in the loop’s block, so after the loop it no longer exists. If it printed 3, your loop used var.
Review the assistant’s code
varanywhere? Replace it:const, orletif the value changes.- A
letthat never gets a new value? Make itconst. - A variable used outside its block? With
let, aReferenceError; withvar, a silent wrong value, as in “Fix the var bug”.
Interview corner
Answer it like an engineer
Answer each one out loud, in three parts, then compare with the model.
- Claim
- Mechanism
- Consequence
-
“
let,constorvar?”Compare with the model
- Claim
constby default,letwhen the value must change,varnever in new code.- Mechanism
letandconstare block-scoped and cannot be used before their declaration;varis function-scoped and holdsundefinedbefore its line.- Consequence
- With
letandconst, a mistake becomes an error at the right line instead of a wrong value.
-
“What is hoisting?”
Compare with the model
- Claim
- The engine registers every declaration of a scope before it runs that scope’s code.
- Mechanism
- A function declaration can be called before its line; a
varexists asundefined;letandconstexist but cannot be touched until their line (the “temporal dead zone”), so using them earlier throws aReferenceError. - Consequence
- Declare before you use; with
letandconst, the engine enforces it.
-
“It’s
const, so why can I still change it?”Compare with the model
- Claim
constmakes the name constant, not the value.- Mechanism
- The name always points to the same array or object;
push, or changing a property, changes its contents;cart = []gives the name a new value and throwsTypeError: Assignment to constant variable. - Consequence
constmeans “this name is never reassigned”. To lock the contents too, useObject.freeze(one level deep).
Quick check · 5 questions
const TAX = 0.2; and later TAX = 0.25;. What happens?
See the answer
const refuses a new value. The error comes when that line runs; the lines above it have already run.
const cart = []; cart.push(10); What is cart now?
See the answer
const locks the name, not the contents. push changes the same array.
What does this print?
if (true) {
let message = 'Hi';
}
console.log(message);
See the answer
let lives in its block. Past the }, message no longer exists.
console.log(n); var n = 3; What does line 1 print?
See the answer
The engine knows var n from the start, as undefined; its value arrives on its line. With let, D would be the answer.
An assistant wrote this. What should you change?
let count = 0;
for (var i = 0; i < items.length; i++) {
count = count + 1;
}
See the answer
count changes, so let is right (const would throw at count = count + 1). i is needed only inside the loop: let keeps it there.
Recap
constby default,letwhen the value changes, nevervar(but recognise it).constlocks the name, not the contents: aconstarray can stillpush.letandconstlive in their block{ };varlives in the whole function.- From inside a block you see out; from outside you cannot see in.
- Before its line:
vargivesundefined,letandconstthrow, a function declaration works.
Sources
- ECMA-262, the 2027 draft: 14.3.1 Let, Const, Using, and Await Using Declarations; 14.3.2 Variable Statement; 14.2 Block.
- MDN: let (block scope, the temporal dead zone); const (“immutable bindings, not immutable values”); var (function scope, undefined until its line).
- MDN: Hoisting; Object.freeze (interview answer 3).