JavaScript Variables: var, let and const
AI-assistedDrafted with AI assistance and reviewed before publishing; examples are version-pinned. Verify against official docs when in doubt.
The first decision in any JavaScript file is which keyword declares the variables. var, let and const behave differently in ways that turn into bugs weeks later, so it is worth settling now.
Environment
Examples run on Node.js 22 LTS:
node -v
# v22.x.x
Save a snippet as demo.js and run node demo.js. Browser console output matches unless a snippet uses a browser-only API.
Three keywords
var a = 1; // function scoped, hoisted
let b = 2; // block scoped, no usable value before declaration
const c = 3; // block scoped, cannot be reassigned
The differences come down to two axes: scope and whether the binding can change.
if (true) {
var x = 10;
let y = 20;
}
console.log(x); // 10 — var leaked out of the block
console.log(y); // ReferenceError: y is not defined
var respects functions, not braces. let and const respect every block, including if, for, and bare {}. That single rule explains most “why did my variable get overwritten” reports.
Hoisting and the temporal dead zone
A var declaration is hoisted to the top of its function and initialized to undefined, so reading before the declaration line does not throw:
console.log(n); // undefined
var n = 5;
let and const are hoisted too, but stay in the temporal dead zone until the declaration executes:
console.log(m); // ReferenceError: Cannot access 'm' before initialization
let m = 5;
That error is a feature: a use-before-initialization bug becomes a stack trace instead of a silent undefined.
const binds a reference, not a value
const nums = [1, 2, 3];
nums.push(4); // allowed: mutating the array
console.log(nums); // [1, 2, 3, 4]
nums = [9]; // TypeError: Assignment to constant variable
const prevents rebinding the name; it does not freeze the object. Object.freeze() does that, and only shallowly.
The default style in modern code: start with const, switch to let only where reassignment is genuinely needed, never use var in new code.
Primitive types and objects
Values split into primitives (immutable, compared by value) and objects (mutable, compared by reference).
| Type | Example | Notes |
|---|---|---|
string |
"hi" |
immutable |
number |
42, 3.14 |
one double-precision type for integers and floats |
boolean |
true |
|
undefined |
undefined |
declared but not assigned |
null |
null |
explicitly “no value” |
symbol |
Symbol("id") |
unique identifier, rare in application code |
bigint |
10n |
integers beyond Number.MAX_SAFE_INTEGER |
object |
{}, [], functions |
reference type, mutable |
typeof inspects them:
typeof 42 // "number"
typeof "hi" // "string"
typeof true // "boolean"
typeof undefined // "undefined"
typeof null // "object" (historical bug, not a real object)
typeof {} // "object"
typeof [] // "object" (arrays are objects)
typeof (() => {}) // "function"
For arrays, always use Array.isArray(value). typeof cannot tell an array from a plain object.
To test for null specifically, use value === null. typeof will not help.
undefined versus null
undefinedis the system’s “no value yet”: an unassigned variable, a function withoutreturn, a missing property.nullis your explicit “this is intentionally empty”.
let user; // undefined: nothing assigned
let currentUser = null; // null: there is no current user
Neither is a string; do not compare against "undefined" or "null".
Common mistakes
1. var in loops
for (var i = 0; ...) leaves i visible after the loop and shares one binding across nested loops. Use let.
2. Redeclaring with var
var x = 1; var x = 2; is legal and silently overwrites. let throws a syntax error, which is what you want.
3. const for something you reassign later
It throws TypeError at the reassignment, not at declaration. If the value changes, declare it with let.
4. Assigning without declaring
count = 1 creates a global in sloppy mode. Add "use strict"; or use ES modules, which are strict by default.
5. Using typeof to detect a property
typeof obj.key === "undefined" cannot distinguish a missing property from a present one holding undefined. Use "key" in obj or Object.hasOwn(obj, "key").
6. Assuming 0.1 + 0.2 === 0.3
Floating point is binary. Compare with a tolerance, or work in integer cents.
Summary
- Default to
const; useletwhen reassignment is required; skipvar. letandconstare block scoped and protected by the temporal dead zone.constprotects the binding, not the contents.Array.isArray()for arrays; remembertypeof null === "object".
Next: operators, coercion, and the ?? versus || distinction.