JavaScript Variables

5 minute read

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 const by default
  • ✅ Use let only when you actually reassign
  • ✅ Declare variables as close to first use as possible (not all at the top)
  • ✅ Use descriptive names (activeUsers, not au)
  • ✅ Use UPPER_SNAKE_CASE for true compile-time constants
  • ❌ Never use var in 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.

← Previous: JS Comments Next: JS Let →

Updated: