let, const, and Scope

Block scope, the temporal dead zone, and why var still causes surprises.

Every example here uses show data, because that is what the app will hold.

Three ways to declare a variable

const names a value you will not replace. let names one you will.

const title = "Severance";
let watched = false;

watched = true;

console.log("title:", title);
console.log("watched:", watched);

watched is now true, because let allows a new value.

Giving a const a new value is an error:

const title = "Severance";
title = "Andor";
// TypeError: Assignment to constant variable.

There is an older keyword, var. Avoid it in new code. The next section shows why.

Block scope

A block is any pair of braces, such as the body of an if. let and const live inside that block. var ignores the braces and can be read afterwards.

const year = 2022;

if (year >= 2020) {
  var era = "modern";
  let label = "Recent show";
  console.log("Inside the block:", label);
}

// Still readable here: var leaked out of the if.
console.log("Outside, var:", era);

try {
  console.log("Outside, let:", label);
} catch (error) {
  console.log("Outside, let:", error.constructor.name + ":", error.message);
}

era is still readable after the if, which is rarely what you meant. Reading label there throws, because that name only exists inside the block.

The temporal dead zone

A let or const name exists from the start of its block. You cannot read it until its declaration runs. That gap is the temporal dead zone. Reading the name inside it throws.

var is different: it starts as undefined, so an early read gives you a confusing value instead of an error.

console.log(year); // undefined — var starts as undefined
console.log(title); // ReferenceError: Cannot access 'title' before initialization

var year = 2022;
let title = "Severance";

The error is the better behaviour. It points at the line with the problem. An undefined would travel on and break somewhere else.

This one is not a sandbox on purpose. The editors here rewrite let to var before running your code, which removes the dead zone. Run it in your own browser console to see the real error.

const is not immutability

const protects the name, not the value. You cannot point the name at another object, but you can change the object it already points at.

const show = { title: "Severance", year: 2021 };

// Allowed: const protects the name, not the object.
show.year = 2022;

console.log(show.title, show.year);

The log reads Severance 2022. The object changed, even though show is a const.

What you cannot do is give show a different object:

const show = { title: "Severance", year: 2021 };
show = { title: "Andor", year: 2022 };
// TypeError: Assignment to constant variable.

Changing an object in place causes bugs later. Most code copies it instead. Destructuring, Spread, and Rest shows how.

Truthy, falsy, and a fallback

|| and ?? both fill in a missing value. They disagree about what missing means.

const show = { title: "Andor", network: "", rating: 0 };

// || falls back on any falsy value, so a real rating of 0 vanishes.
console.log("|| network:", show.network || "Unknown network");
console.log("|| rating:", show.rating || "not rated");

// ?? falls back only on null and undefined, so 0 survives.
console.log("?? network:", show.network ?? "Unknown network");
console.log("?? rating:", show.rating ?? "not rated");

|| hides the real rating of 0. ?? keeps the 0. It also keeps the empty network name, so that line is blank.

|| falls back on any falsy value. Six values are falsy: false, 0, "", null, undefined, and NaN. Everything else is truthy.

?? falls back only on null and undefined. Use it for a number that can be 0. Use || when an empty string should count as missing.

Common mistakes

  • Reaching for let first. Start with const, and change it only when you give the name a new value.
  • Expecting const to freeze an object. It does not; the fields can still change.
  • Using || for a rating. A real 0 is falsy, so || hides it. Use ??.

Exercise

The line below should print the real rating. Andor has a rating of 0, so the console should read:

Rating: 0

It prints Rating: not rated instead. Change one operator.

const show = { title: "Andor", rating: 0 };

console.log("Rating:", show.rating || "not rated");