Variables, Scope and Types
const/let/var), where a name is visible
(scope), which kinds of values exist (the primitive types), and the one copy-behaviour
distinction that explains half of all beginner bugs: value versus reference.
What a variable is
A named binding to a value
A variable is a name that the engine attaches to a value. You create one with a declaration; afterwards the name stands in for the value everywhere in your code. Start with the strictest declaration and see what the engine says:
const clicks = 0; // declare: create the name, give it a value
clicks = 1; // try to rebind it — predict the result
You should see:
Uncaught TypeError: Assignment to constant variable.
const means "this name binds this value forever". Use let when the
value genuinely must change:
let clicks = 0;
clicks = 1; // fine — let allows reassignment
clicks = clicks + 10; // read the old value, compute, store back
console.log(clicks); // 11
const is the default — here is why
The rule of thumb: declare everything const; switch to
let only when reassignment is genuinely needed. A name that cannot be reassigned
cannot be reassigned by accident — the engine checks a whole class of bug for you.
var is the pre-2015 declaration: it works, but its scope rules surprise (below).
You will meet it in older code; never write it.
The temporal dead zone
Declarations are hoisted — but not usable
The engine reads the whole block first and registers every name before running a
line — that is hoisting. But const/let names stay
unusable until their declaration line runs. The gap between "the name exists" and "the name
works" is the temporal dead zone (TDZ):
console.log(user); // runs BEFORE the declaration line below
const user = "Ada";
You should see:
Uncaught ReferenceError: Cannot access 'user' before initialization
var hoists differently — the classic trap
console.log(count); // no error! prints...
var count = 5;
You should see undefined: var names are hoisted
with the value undefined. A silent undefined is worse than a
loud error — one more reason var is retired.
Scope
Block scope
A block is any { ... } pair — an if, a loop body, a
function body. let and const names exist only inside the block
where they were declared. Outside, the name never existed:
if (true) {
const status = "active";
console.log(status); // works — inside the block
}
console.log(status); // ReferenceError — status never existed out here
Scope is one-way glass: inner code sees outward through every level; outer code sees nothing in.
Shadowing
An inner block may declare a name that also exists outside. Inside, the inner one shadows the outer:
const name = "Ada";
if (true) {
const name = "Grace"; // a DIFFERENT variable, temporarily hiding the first
console.log(name); // Grace
}
console.log(name); // Ada — untouched
Legal, but in small scripts shadowing is more often a typo than a decision — if two visible variables share a name, rename one.
The primitive types
Seven types; everything else is an object
console.log(typeof "hi"); // string
console.log(typeof 5, typeof 5.5); // number, number — one type for all numbers
console.log(typeof true); // boolean
console.log(typeof undefined); // undefined
console.log(typeof null); // "object" — a language bug, kept for compatibility
console.log(typeof 10n); // bigint — huge integers
console.log(typeof Symbol()); // symbol — unique identifiers
null versus undefined: undefined means "never given a
value"; null means "deliberately empty". And NaN ("not a number") is
the result of broken arithmetic — type number, and famously not equal to itself;
test for it with Number.isNaN(x).
Value versus reference — the half-of-all-bugs distinction
Primitives copy the value; objects copy the address. One picture, dozens of bugs explained.
// Primitives: independent copies.
let a = 5;
let b = a;
b = 99;
console.log(a); // 5 — untouched
// Objects: two names, ONE object.
const original = { score: 10 };
const alias = original;
alias.score = 99;
console.log(original.score); // 99 — "the other one" changed too
Nothing changed "behind your back": there is only one object, and both names are doors to it. Arrays, dates and every other non-primitive behave the same way.
Naming and reading style
Names that explain themselves
Names should make code readable without comments: attemptsLeft beats
x; booleans read as yes/no questions (isLoggedIn,
hasErrors). One concept, one name — resist data, info,
temp.
Practice: name it, declare it, lose it
The task
Run foundations/09_values_and_types.js with node and predict every
line first. Then write a block in the console where an inner shadowed name hides the outer one
— and confirm the outer value after the block.
The checklist
- You can explain
constvsletin one sentence each — and whyconstis the default. - You have triggered the TDZ error and the
var-hoistingundefinedon purpose. - You can predict whether an assignment copies a value or an address.
- You know
typeof nulllies, and how to test forNaNcorrectly.