AceDevHub
Runtime & SyntaxFree

Variables: let, const, and var

Declare and assign values with let, const, and var — and understand block scope, reassignment, and why var is legacy.

BeginnerFreeSyntax

Variables are how you name values the program must remember. Sounds trivial — but most subtle JavaScript bugs trace back to scope (who can see the name?) and mutability (can the binding or the value change?). This lesson builds the mental model production teams expect: const by default, let when you must reassign, and var only when reading legacy code.

Variables name values in memory

A variable is a binding between an identifier and a value in memory — not the value itself. When you write const user = { name: "A" }, the name user points at an object on the heap. Reassigning user would point the name elsewhere; mutating user.name changes the object both names would share if you copied the reference. JavaScript gives you three declaration keywords with different scope and hoisting rules; only const and let belong in code you write today.

KeywordReassign?ScopeUse in 2026 code?
constBinding fixed; object contents can mutateBlockDefault choice
letYesBlockWhen reassignment is required
varYesFunction (or global)Legacy only — avoid

const — default to immutability of the binding

const means the identifier cannot be rebound to another value. It does not mean the value is deeply immutable — const arr = [] still allows push and pop because you are mutating the array object, not replacing the arr binding. Teams use const to signal intent: this name always refers to the same thing for the rest of the block. When you need to reassign (counters, swapping values, re-fetching in a loop), switch to let in that narrow case.

const-demo.js
Loading editor…
Outputconsole
{ name: 'Sangam', score: 11 }

let — block-scoped reassignment

Block scope means a variable declared inside { } dies at the closing brace. That matches how humans read indentation — a message declared inside an if block should not leak into the rest of the function. let was added to JavaScript specifically to fix the visual vs actual scope mismatch that var created for decades.

let-block-scope.js
Loading editor…
Outputconsole
inside block
1

var — function scope and the classic loop bug

var ignores block boundaries. Its declaration is hoisted to the top of the enclosing function (or global scope) and initialized to undefined until the assignment line runs. That is why var i in a loop plus async callbacks produced the classic bug: one shared i, not one per iteration. let creates a fresh binding per iteration in for loops, which is why modern code behaves intuitively with closures inside loops.

var-vs-let-loop.js
Loading editor…
Outputconsole
var: 3
var: 3
var: 3
let: 0
let: 1
let: 2

Hoisting preview (full lesson later)

Hoisting is often explained poorly as "functions move to the top." More precisely: the engine registers bindings during compile phase before execution. var bindings exist as undefined early; let/const bindings exist but sit in the temporal dead zone until their declaration line runs — accessing them before that line throws ReferenceError, which prevents the silent undefined bugs var allowed.

hoisting-preview.js
Loading editor…
Outputconsole
undefined
block scoped

Naming and declaration style

  • Use camelCase for variables: userCount, isActive
  • Use UPPER_SNAKE for true constants: MAX_RETRIES = 3
  • Declare one logical concept per line — avoid comma-chained var a = 1, b = 2
  • Initialize when you know the value — reduces undefined bugs
  1. 1Default to const; reach for let only when reassigning
  2. 2Avoid var in new code — block scope matches visual structure
  3. 3const does not deep-freeze objects — clone if you need immutability
  4. 4Next lesson: primitive types, objects, and typeof