JavaScript Variables: var, let and const

Chapter 1 of 16

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

  • undefined is the system’s “no value yet”: an unassigned variable, a function without return, a missing property.
  • null is 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; use let when reassignment is required; skip var.
  • let and const are block scoped and protected by the temporal dead zone.
  • const protects the binding, not the contents.
  • Array.isArray() for arrays; remember typeof null === "object".

Next: operators, coercion, and the ?? versus || distinction.