JavaScript Variables
A variable is a named container for a value. You store a number, a string, an object — anything — under a label, then refer to it by that label later. JavaScript gives you three ways to declare a variable (var, let, const), but in modern code you’ll use exactly two of them. The third is a historical artefact you should recognise but never write.
Declaring a Variable
let name = "Ada";
const PI = 3.14159;
var legacy = "don't use this";
const— value can’t be reassigned (your default)let— value can be reassigned (when you actually need to)var— old way, has confusing scoping rules (avoid)
The pattern is: declare the variable with a keyword, then assign a value with =.
A Variable’s Three Parts
let userName = "Ada" ;
│ │ │ │
│ │ │ └── value (the actual data)
│ │ └────────── assignment operator
│ └────────────────── identifier (the name)
└───────────────────────────── declaration keyword
You can declare without assigning — the variable holds undefined:
let count;
console.log(count); // undefined
count = 5;
console.log(count); // 5
You can also declare several variables at once:
let a = 1, b = 2, c = 3;
// Works, but most style guides prefer one per line:
let a = 1;
let b = 2;
let c = 3;
Naming Rules — A Quick Recap
(Covered in detail in the Syntax chapter.)
| Allowed | Not Allowed |
|---|---|
Letters, digits, _, $ |
Starts with a digit |
camelCase (preferred) |
Hyphens, spaces, punctuation |
| Unicode letters | Reserved keywords (class, if, return…) |
// ✅ Legal
let userName;
let _temp;
let $element;
let π = 3.14;
// ❌ Illegal
let 2name; // starts with digit
let user-name; // hyphen
let class; // reserved
var — The Old Way (Don’t Use)
var was the only way to declare variables before ES6 (2015). It has two quirks that cause real bugs:
// 1. var is function-scoped, not block-scoped
if (true) {
var leaked = "hello";
}
console.log(leaked); // "hello" ← escaped the block!
// 2. var can be redeclared silently
var name = "Ada";
var name = "Linus"; // no error, but you might not have meant to overwrite
Compare with let:
if (true) {
let safe = "hello";
}
console.log(safe); // ❌ ReferenceError — block-scoped
let name = "Ada";
let name = "Linus"; // ❌ SyntaxError — already declared
Warning: You’ll see var in old tutorials, legacy code, and copy-pasted snippets. Recognise it, but never write it in new code.
let vs const vs var — Side by Side
| Feature | var |
let |
const |
|---|---|---|---|
| Scope | Function | Block { } |
Block { } |
| Hoisting | Yes (init to undefined) |
Yes (but TDZ) | Yes (but TDZ) |
| Redeclaration | ✅ allowed | ❌ SyntaxError | ❌ SyntaxError |
| Reassignment | ✅ allowed | ✅ allowed | ❌ TypeError |
| Must initialize | ❌ no | ❌ no | ✅ yes |
| Use in new code | ❌ never | ✅ when value changes | ✅ default |
// The rule of thumb in modern JavaScript:
const x = 1; // default
let y = 1; y = 2; // only when you reassign
var z = 1; // never
What “Block-Scoped” Means
A block is anything inside { }: function bodies, if statements, loops, even a bare pair of braces. let and const exist only inside the block they’re declared in.
for (let i = 0; i < 3; i++) {
const message = `iteration ${i}`;
console.log(message);
}
// console.log(i); // ❌ ReferenceError
// console.log(message); // ❌ ReferenceError
This is the property that makes let/const predictable. (var ignores blocks entirely.)
Hoisting in One Paragraph
JavaScript hoists declarations to the top of their scope. With var, you can use the variable before declaring it (it’s undefined). With let/const, accessing it before the declaration line throws a ReferenceError — that gap is called the Temporal Dead Zone.
console.log(a); // undefined (var is hoisted and initialized)
var a = 1;
console.log(b); // ❌ ReferenceError (TDZ)
let b = 1;
Hoisting has its own chapter (32) — for now, just declare your variables before you use them, and you’ll never hit TDZ surprises.
Assignment vs Declaration vs Initialization
A common source of confusion:
let x; // declaration
x = 5; // assignment
let y = 5; // declaration + assignment (a.k.a. "initialization")
You can re-assign (with let):
let count = 0;
count = count + 1;
count = count * 2;
console.log(count); // 2
You cannot re-declare in the same scope:
let count = 0;
let count = 5; // ❌ SyntaxError
What const Really Locks
const prevents reassignment of the binding. It does not freeze the value itself. If the value is an object or array, you can still mutate its contents.
const PI = 3.14;
PI = 3.15; // ❌ TypeError
const user = { name: "Ada" };
user.name = "Linus"; // ✅ works — we're mutating the object, not rebinding
user = {}; // ❌ TypeError — can't reassign
const tags = ["red", "blue"];
tags.push("green"); // ✅ works
tags = []; // ❌ TypeError
Chapter 9 covers const in depth, including Object.freeze() for true immutability.
Type Is Determined by the Value
JavaScript is dynamically typed — a variable’s type is whatever value is currently stored in it. The same variable can hold different types over time (though doing so is usually a smell).
let value = 5;
console.log(typeof value); // "number"
value = "hello";
console.log(typeof value); // "string"
value = true;
console.log(typeof value); // "boolean"
| Concept | JavaScript | Java / C# |
|---|---|---|
| Type system | Dynamic — values have types | Static — variables have types |
| Type at declaration | Not specified | Required (int, String, …) |
| Type-checking | Runtime only | Compile time |
A Realistic Example
// Configuration that never changes ─→ const
const API_BASE = "https://api.example.com";
const MAX_RETRIES = 3;
// State that evolves ─→ let
let attempt = 0;
let lastError = null;
async function callApi(path) {
while (attempt < MAX_RETRIES) {
try {
const res = await fetch(`${API_BASE}${path}`); // const inside loop
if (res.ok) return res.json();
throw new Error(`HTTP ${res.status}`);
} catch (err) {
lastError = err; // reassign — needs let
attempt += 1; // reassign — needs let
}
}
throw lastError;
}
The pattern is consistent: const everywhere by default, swap to let only when you genuinely need to reassign.
Best Practices Checklist
- ✅ Use
constby default - ✅ Use
letonly when you actually reassign - ✅ Declare variables as close to first use as possible (not all at the top)
- ✅ Use descriptive names (
activeUsers, notau) - ✅ Use
UPPER_SNAKE_CASEfor true compile-time constants - ❌ Never use
varin new code - ❌ Don’t reuse the same variable for different types of value
- ❌ Don’t declare a variable until you have something to put in it
Tip: ESLint with the prefer-const and no-var rules will catch every violation automatically. Configure it once and forget about it.
What’s Next?
Now you understand variables in general — let’s dig into let specifically: block scope, the temporal dead zone, and why let quietly fixed the most painful bug of var loops.