Variables (var, let, const)
A variable is a named place to store a value. JavaScript gives you three keywords to create one — var, let and const — and they behave differently enough that the choice affects scoping, hoisting, redeclaration and intent. Modern code uses const and let; var is mostly history, but you'll meet it.
Declaration and assignment
Declaring a variable introduces a new name. Assigning puts a value in it. You can do both at once, or split them.
let count; // declaration only — count is now undefined count = 0; // assignment let total = 100; // declaration + initial value const PI = 3.14159; // const REQUIRES an initial value
A declared-but-unassigned variable holds undefined. A non-existent variable throws a ReferenceError — see typeof later for a way to test without crashing.
The three keywords compared
Each keyword answers three questions:
Scope — where is the name visible?
Re-declaration — can you declare the same name twice in the same scope?
Re-assignment — can you change what the name refers to?
Side-by-side
// var: function-scoped, re-declarable, re-assignable var a = 1; var a = 2; // ok a = 3; // ok // let: block-scoped, NOT re-declarable in the same block, re-assignable let b = 1; // let b = 2; // SyntaxError: Identifier 'b' has already been declared b = 3; // ok // const: block-scoped, NOT re-declarable, NOT re-assignable const c = 1; // c = 2; // TypeError: Assignment to constant variable // const c = 2; // SyntaxError
const user = { name: "Ada" };
user.name = "Lin"; // ok — mutating the object
user.role = "admin"; // ok — adding a property
// user = { name: "Lin" }; // TypeError — re-binding is not allowedBlock scope vs function scope
let and const are block-scoped — they live inside the nearest { ... }. var is function-scoped — it leaks out of inner blocks and is visible across the entire enclosing function.
function demo() {
if (true) {
var x = 1;
let y = 2;
const z = 3;
}
console.log(x); // 1 — var leaked
console.log(y); // ReferenceError
console.log(z); // ReferenceError
}
demo();That single difference is the single biggest reason to prefer let/const. Block scoping makes code easier to reason about, and it eliminates a class of bugs around loops and closures.
The temporal dead zone (TDZ)
let and const variables exist from the start of their block, but cannot be read or written until the line that declares them runs. The interval between the block opening and the declaration is the temporal dead zone.
{
// console.log(value); // ReferenceError — TDZ
let value = 42;
console.log(value); // 42
}This sounds annoying but it is a feature: it forces you to declare variables before you use them, catching typos and ordering bugs at runtime instead of silently giving you undefined.
Hoisting — a quick preview
Hoisting is the engine's habit of processing declarations before running the code. The three keywords all hoist, but only var initialises to undefined immediately.
console.log(a); // undefined — declared, not yet assigned console.log(b); // ReferenceError — in the TDZ var a = 1; let b = 2;
undefined ReferenceError: Cannot access 'b' before initialization
The full story lives on the Hoisting page. For now: declare your variables at the top of their scope and you never need to think about it.
Multiple declarations on one line
let x = 1, y = 2, z = 3; // readable when values are related; ugly when they aren't. // Most style guides prefer one declaration per line: let firstName = "Ada"; let lastName = "Lovelace";
Naming variables
Use camelCase:
userName,currentScore,isLoggedIn.Use descriptive names —
i,j,kare fine for tiny loops; everywhere else, use words.Booleans read well with
is,hasorcanprefixes:isReady,hasErrors,canEdit.Constants you want to flag as configuration are conventionally UPPER_SNAKE_CASE:
MAX_RETRIES = 3.Avoid one-character names except in the smallest local scopes (e.g. a math expression or a one-line callback).
When to use which
Default to
const. It signals "I won't re-assign this" — useful information for readers and for the engine.Use
letwhen you genuinely need to re-assign — counters, accumulators, variables conditionally assigned in aswitch.Avoid
varin new code. The only reason to use it today is editing legacy code where it would be disruptive to change.