Mehdi Tmimi JavaScript

JavaScript Chapter 02 · The language

How do variables work in JavaScript?

Every file, and every answer an assistant gives you, is full of const, let and var. Which one should you keep, and where can you use it?

The language is the core, the same in every engine. This chapter starts there: its variables.

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.

const, let, and not var

cart.js
const TAX = 0.2;     // never gets a new value
let total = 0;       // changes as items are added
total = total + 10;
  • const by default. Give TAX a new value and the program stops with a TypeError.
  • let when the value must change, like a running total.
  • var is 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.

  1. Inside the loop. With let, i lives in the loop; with var, i lives in the whole function. Line 4 uses total from the box around it: from inside, you see out.
  2. After the loop. console.log(i) with let: ReferenceError: i is not defined, and the program stops. With var: 2, and the program goes on, no warning.
  3. Before its line. console.log(total) above let total = 0;: ReferenceError: Cannot access 'total' before initialization, and the program stops (const too). Above var 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:

snippet
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

The rule of this course

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

snippet
const TAX = 0.2;
const cart = [];
cart.push(10);
cart.push(5);
console.log(cart);
TAX = 0.25;
console.log(TAX);
Predict first

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

snippet
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 first

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

snippet
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]));
  1. Run it. The cart is 10 + 20 + 30: you expect 60. What prints?
  2. Replace every var with let and run again. Read the error: which line?
  3. 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

snippet
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)
  1. Follow the four comments, then Run: you should get 60.
  2. Add console.log(i) after the loop. Which error, and why?
Check after you try

One possible answer:

snippet
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

  1. var anywhere? Replace it: const, or let if the value changes.
  2. A let that never gets a new value? Make it const.
  3. A variable used outside its block? With let, a ReferenceError; with var, 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.

  1. Claim
  2. Mechanism
  3. Consequence
  1. “let, const or var?”

    Compare with the model
    Claim
    const by default, let when the value must change, var never in new code.
    Mechanism
    let and const are block-scoped and cannot be used before their declaration; var is function-scoped and holds undefined before its line.
    Consequence
    With let and const, a mistake becomes an error at the right line instead of a wrong value.
  2. “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 var exists as undefined; let and const exist but cannot be touched until their line (the “temporal dead zone”), so using them earlier throws a ReferenceError.
    Consequence
    Declare before you use; with let and const, the engine enforces it.
  3. “It’s const, so why can I still change it?”

    Compare with the model
    Claim
    const makes 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 throws TypeError: Assignment to constant variable.
    Consequence
    const means “this name is never reassigned”. To lock the contents too, use Object.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

  1. const by default, let when the value changes, never var (but recognise it).
  2. const locks the name, not the contents: a const array can still push.
  3. let and const live in their block { }; var lives in the whole function.
  4. From inside a block you see out; from outside you cannot see in.
  5. Before its line: var gives undefined, let and const throw, a function declaration works.

Sources

← All chapters