var vs let vs const in JavaScript: Which to Use and Why
Three ways to declare a variable in JavaScript, and why modern code uses const by default, let when the value changes, and var almost never. Scope, hoisting, reassignment vs mutation, and the classic loop bug — with short examples.
JavaScript has three keywords for creating a variable: var, let and const. The modern rule fits on one line:
Use
constby default. Useletwhen you need to reassign. Don't usevar.
Here's why.
const: can't be reassigned
const price = 20
price = 25 // TypeError: Assignment to constant variable.
Use const for anything that won't be pointed at a new value — which is most variables. It makes code easier to read: you know the value won't change further down.
const doesn't mean "frozen"
This trips up everyone once:
const cart = ['apple']
cart.push('pear') // fine — the array itself changed
cart = ['banana'] // error — can't point cart at a new array
const stops reassignment, not mutation. The array or object it holds can still be changed. (Object.freeze() exists if you truly need immutability.)
let: can be reassigned
let attempts = 0
attempts = attempts + 1 // fine
Use let for counters, values that change in a loop, or a variable you set in an if branch.
var: the old way
var was the only option before 2015. It still works, but it has two behaviours that cause bugs.
1. Function scope instead of block scope
let and const exist only inside the nearest { } block. var ignores blocks and belongs to the whole function:
if (true) {
var a = 1
let b = 2
}
console.log(a) // 1 — leaked out of the block
console.log(b) // ReferenceError: b is not defined
2. Hoisting to undefined
var declarations are moved to the top of the function and start as undefined, so using one too early silently gives undefined:
console.log(total) // undefined (no error!)
var total = 10
With let or const, the same code throws an error — which is what you want, because it's a bug. (is not defined errors)
The classic loop bug
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100)
}
// prints 3, 3, 3
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100)
}
// prints 0, 1, 2
With let, each loop iteration gets its own i. With var, they all share one, which has reached 3 by the time the timers fire.
Quick reference
var |
let |
const |
|
|---|---|---|---|
| Scope | Function | Block | Block |
| Reassign | Yes | Yes | No |
| Redeclare in same scope | Yes | No | No |
| Use before declaration | undefined |
Error | Error |
| Use in new code? | No | When reassigning | By default |
In practice
- Linters like ESLint can enforce this automatically with the
no-varandprefer-construles. (Linters and formatters explained) - In React components, almost everything is
const— including state fromuseState, because you change it with the setter, not by reassigning. (React useState explained) - If AI-generated code uses
var, it's often copied from an old pattern. Safe to switch tolet/const, but run the code afterwards.
EasySpawn gives you a server where your JavaScript app runs, with Claude Code ready to explain and tidy any code you didn't write yourself. See how it works or join the waitlist.
Related: JavaScript vs TypeScript · Async/Await in JavaScript · JavaScript Array Methods · HTML, CSS, and JavaScript Explained
Keep reading
"ReferenceError: window is not defined" in Next.js: How to Fix It
window, document and localStorage only exist in the browser. Next.js renders components on the server first, where they don't exist. Why it happens, and the fixes: useEffect, 'use client', typeof checks, dynamic imports with ssr: false, and libraries that touch window on import.
What Is TypeScript? JavaScript With Labels, Explained for Beginners
TypeScript is JavaScript plus types: labels that say what kind of value each thing holds, checked before your code runs. What it looks like, why AI tools default to it, what .ts and .tsx files are, and how to read the red squiggles.