let in depth
The Three Ways to Name Things in JavaScript
In JavaScript, before you can use a value, you need to give it a name — a container to hold it. There are three ways to do this: var, let, and const. They look similar but behave very differently. Understanding why they differ will save you from bugs that are hard to trace.
The Big Picture First
Think of your code as a building with rooms. Some rooms are small (a single function), some are large (the entire file). When you create a named value, JavaScript needs to know: which room does this name belong to? That's the core question behind all three keywords.
var — The Old Way (and Why It's Tricky)
var was the only way to name things in JavaScript for nearly 20 years. It works, but it has two behaviors that surprise people.
1. var ignores block boundaries
A block is anything inside { } — an if statement, a for loop, etc. var does not respect these walls. It escapes out of blocks and belongs to the nearest function (or the whole file, if there's no function).
if (true) {
var age = 25;
}
console.log(age); // 25 — it leaked out!
With let or const, this would throw an error. With var, it silently works. This is called function scope — var only cares about functions, not other blocks.
function greet() {
if (true) {
var name = "Arjun";
}
console.log(name); // "Arjun" — still works inside the function
}
greet();
console.log(name); // Error — can't escape the function though
2. var is hoisted (and silently set to undefined)
JavaScript reads your entire file before running it. When it sees a var, it moves the declaration to the top of the function — but not the value. This is called hoisting.
console.log(city); // undefined — no error, but no value yet
var city = "Kolkata";
console.log(city); // "Kolkata"
JavaScript secretly rewrites this as:
var city; // moved to top
console.log(city); // undefined
city = "Kolkata"; // assigned here
console.log(city); // "Kolkata"
This is confusing. You used city before you defined it, and JavaScript didn't complain — it just said "it exists, but has no value yet."
3. var can be re-declared
var score = 10;
var score = 99; // No error! Just overwrites silently.
This makes bugs hard to find in large files.
let — The Better var
let was introduced in 2015 to fix var's problems. Use it when you have a value that will change over time.
1. let respects blocks
if (true) {
let temperature = 38;
}
console.log(temperature); // Error — temperature doesn't exist here
The value stays inside the { } it was created in. This is called block scope.
2. let is hoisted but not silently set to undefined
let is also hoisted (JavaScript knows it exists), but if you try to use it before the line where it's defined, you get an actual error instead of a silent undefined.
console.log(score); // ReferenceError — can't use before initialization
let score = 50;
This is the Temporal Dead Zone (TDZ) — the gap between the start of the block and where the let line actually appears. The name sounds scary, but it just means: "don't use it before you define it."
3. let cannot be re-declared in the same block
let points = 10;
let points = 20; // Error — already declared
But you can absolutely reassign it:
let points = 10;
points = 20; // Fine — this is what let is for
A common use case — loops
for (let i = 0; i < 3; i++) {
console.log(i); // 0, 1, 2
}
console.log(i); // Error — i only existed inside the loop
With var, that last line would print 3 — a stale value leaking out of the loop. let keeps it clean.
const — When the Name Shouldn't Change
const is like let in every way — block scoped, no silent hoisting — except you must assign a value immediately, and you cannot reassign it later.
const gravity = 9.8;
gravity = 10; // Error — you cannot reassign a const
const PI; // Error — must assign a value right away
const does NOT mean the value is frozen
This is the most important thing to understand about const. It means: the name will always point to the same thing. But if that thing is an object or array, the contents can still change.
const student = { name: "Priya", marks: 85 };
student.marks = 90; // Fine — we changed what's inside
student = { name: "Raj" }; // Error — we tried to replace the whole object
Think of const as a label stuck to a box. You can't move the label to a different box. But you can put new things inside the same box.
Side by Side
var | let | const | |
|---|---|---|---|
| Scope | Function | Block | Block |
| Hoisted? | Yes (as undefined) | Yes (but unusable until defined) | Yes (but unusable until defined) |
| Re-declare? | ✅ Yes | ❌ No | ❌ No |
| Reassign? | ✅ Yes | ✅ Yes | ❌ No |
| Must assign at creation? | ❌ No | ❌ No | ✅ Yes |
What to Actually Use
A simple rule that works 95% of the time:
- Use
constby default. If you're not sure, start withconst. - Switch to
letonly when you know the value needs to change (a counter, a flag that flips, etc.). - Avoid
varentirely in new code. It exists for historical reasons and compatibility with old browsers, not because it's a good idea.
const MAX_SPEED = 340; // will never change
const user = { name: "Dev" }; // object won't be replaced
let attempts = 0; // will be incremented
let isLoggedIn = false; // will flip to true
attempts++; // fine
isLoggedIn = true; // fine
MAX_SPEED = 400; // Error — good, we don't want this
One Last Thing — Global Scope
If you write var outside any function or block, the name gets attached to the global window object in browsers. let and const do not do this — they remain in the file's own scope.
var x = 1;
let y = 2;
console.log(window.x); // 1 — leaked onto window
console.log(window.y); // undefined — safely contained
This is another reason to prefer let and const. Polluting the global object causes conflicts when your code runs alongside other scripts.