JavaScriptVariables (var, let, const)

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.

JS
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

JS
// 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` freezes the binding, not the value
`const` means the *variable* always points to the same value. If that value is an object or array, you can still mutate its contents — `const arr = []; arr.push(1)` is fine. To freeze the value too, use `Object.freeze` or pick an immutable data structure.

JS
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 allowed
Block 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.

JS
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.

JS
{
  // 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.

JS
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

JS
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, k are fine for tiny loops; everywhere else, use words.

  • Booleans read well with is, has or can prefixes: 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 let when you genuinely need to re-assign — counters, accumulators, variables conditionally assigned in a switch.

  • Avoid var in new code. The only reason to use it today is editing legacy code where it would be disruptive to change.

Const-by-default is a readability win
Once you trust that every `let` in the file is going to change, your eye scans `const` lines as "this name = this value, done" and slows down on `let` lines to track the mutation. That tiny extra signal compounds in real code.
Don't forget the keyword
Forgetting `let`/`const` doesn't error in non-strict mode — it silently creates a *global* variable, which is almost always a bug. `"use strict"` and ES modules turn this into a `ReferenceError`, which is exactly what you want.