Question
JavaScript let vs var: Scope, Hoisting, and When to Use Each
Question
ECMAScript 6 introduced the let declaration keyword.
I have heard that it is described as a local variable, but I am still not fully sure how it behaves differently from the var keyword.
What are the differences between let and var in JavaScript? When should let be used instead of var?
Short Answer
By the end of this page, you will understand how let and var differ in scope, hoisting, redeclaration, and global behavior in JavaScript. You will also learn why modern JavaScript usually prefers let and const over var, and how to avoid common bugs caused by var.
Concept
let and var are both used to declare variables in JavaScript, but they follow different rules.
The biggest difference is scope:
varis function-scopedletis block-scoped
A block is any code inside curly braces {} such as an if, for, or while statement.
if (true) {
var a = 10;
let b = 20;
}
console.log(a); // 10
console.log(b); // ReferenceError
Here, a is still available outside the if block because var ignores block scope. But b only exists inside the block because let respects block scope.
Another important difference is hoisting.
Both var and let are hoisted, which means their declarations are processed before code runs. But they behave differently:
varis hoisted and initialized withundefinedletis hoisted but not initialized until its declaration is reached
console.log(x); // undefined
var x = 5;
console.log(y); // ReferenceError
let y = 5;
The let example fails because y is in the temporal dead zone: the period between the start of the scope and the line where the variable is declared.
let also prevents accidental redeclaration in the same scope:
let count = 1;
let count = 2; // SyntaxError
But var allows it:
var count = 1;
var count = 2; // allowed
This matters because silent redeclarations can hide bugs.
In real programming, let matters because it makes variable behavior more predictable. It reduces scope-related mistakes, especially in loops, conditionals, and larger codebases.
Mental Model
Think of var as a label attached to an entire room, while let is a label attached to a specific box inside the room.
- With
var, if you declare a variable anywhere in a function, it is available across the whole function. - With
let, the variable only exists inside the exact block where you created it.
Another way to think about it:
varis like a note written on the wall of the whole function.letis like a sticky note placed on one section only.
This is why let gives you tighter control. Variables stay closer to where they are actually used.
Syntax and Examples
Basic syntax
var name = "Alice";
let age = 25;
Both declare variables, but they behave differently.
Scope example
function test() {
if (true) {
var city = "Paris";
let country = "France";
}
console.log(city); // "Paris"
console.log(country); // ReferenceError
}
Why this happens
cityusesvar, so it belongs to the whole functiontestcountryuseslet, so it exists only inside theifblock
Loop example
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log("var:", i), 100);
}
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log("let:", j), 100);
}
Output
var: 3
var: 3
var: 3
let: 0
let: 1
let: 2
Why this happens
var iis shared across all loop iterationslet jcreates a new binding for each iteration
This is one of the most practical reasons to prefer let in loops.
Redeclaration example
var score = 10;
var score = 20; // allowed
let level = 1;
// let level = 2; // SyntaxError
let helps catch mistakes earlier.
Step by Step Execution
Consider this example:
function demo() {
console.log(a);
// console.log(b);
var a = 1;
let b = 2;
if (true) {
var aInside = "var value";
let bInside = "let value";
}
console.log(a); // 1
console.log(b); // 2
console.log(aInside); // "var value"
// console.log(bInside); // ReferenceError
}
demo();
What happens step by step
- The function
demostarts running. - JavaScript sees
var aand hoists it, initializing it toundefined. - JavaScript also hoists
let b, but does not initialize it yet. console.log(a)printsundefined.- If
console.log(b)were uncommented here, it would throw aReferenceErrorbecausebis still in the temporal dead zone. var a = 1assigns1toa.let b = 2creates and initializesb.- The
ifblock runs. var aInsidebecomes available to the whole function.let bInsideexists only inside theifblock.- After the block ends,
aInsideis still accessible. - After the block ends,
bInsideno longer exists.
This example shows all the major differences together: hoisting, scope, and block visibility.
Real World Use Cases
let is useful whenever a variable should only exist in a small area of code.
Common real-world examples
Inside loops
for (let index = 0; index < items.length; index++) {
console.log(items[index]);
}
Each iteration gets the correct index.
Temporary calculation values
function calculateTotal(price, taxRate) {
let tax = price * taxRate;
let total = price + tax;
return total;
}
The variables only exist where they are needed.
Conditional logic
if (user.isAdmin) {
let message = "Access granted";
console.log(message);
}
message does not leak outside the block.
API or form validation code
function validateEmail(email) {
let isValid = email.includes("@");
return isValid;
}
Modern apps often rely on small, clearly scoped variables like this.
var is mostly seen in:
- older JavaScript codebases
- legacy tutorials
- code written before ES6 became standard
In new code, var is usually avoided unless you are maintaining older code or specifically need its function-scoped behavior.
Real Codebase Usage
In real projects, developers usually follow this pattern:
- use
constby default - use
letwhen reassignment is needed - avoid
var
Typical pattern
const apiUrl = "/users";
let retries = 0;
while (retries < 3) {
retries++;
}
Why teams avoid var
Fewer scope bugs
let keeps variables inside the block where they are used.
Safer refactoring
If code is moved into an if block or loop, let is less likely to affect surrounding code unexpectedly.
Better loop behavior
let works naturally with asynchronous callbacks inside loops.
Common patterns in codebases
Guard clauses
function processUser(user) {
if (!user) {
return;
}
let name = user.name.trim();
console.log(name);
}
Validation
function submit(data) {
let errors = [];
if (!data.email) {
errors.push("Email is required");
}
return errors;
}
Transforming data
function formatUsers(users) {
return users.map((user) => {
let fullName = `${user.firstName} ${user.lastName}`;
return fullName;
});
}
These examples show how let fits naturally into modern JavaScript structure.
Common Mistakes
1. Assuming var is block-scoped
Broken code:
if (true) {
var message = "hello";
}
console.log(message); // still works
Why it is a problem:
You may expect message to disappear after the block, but it does not.
How to avoid it:
Use let when you want block scope.
if (true) {
let message = "hello";
}
console.log(message); // ReferenceError
2. Using a let variable before declaring it
Broken code:
console.log(total);
let total = 100;
Why it is a problem:
let is hoisted but not initialized before the declaration line.
How to avoid it:
Declare variables before using them.
3. Redeclaring let in the same scope
Broken code:
let value = 1;
let value = 2;
Why it is a problem:
This causes a SyntaxError.
How to avoid it:
Reassign instead of redeclare.
let value = 1;
value = 2;
4. Using var in asynchronous loops
Broken code:
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
Output:
3
3
3
How to avoid it:
Use let in the loop.
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
5. Confusing let with const
let means the variable can be reassigned.
let count = 1;
count = 2; // allowed
If the value should not be reassigned, use const instead.
Comparisons
| Feature | var | let |
|---|---|---|
| Scope | Function scope | Block scope |
| Hoisted | Yes | Yes |
| Accessible before declaration | Yes, as undefined | No, causes ReferenceError |
| Redeclaration in same scope | Allowed | Not allowed |
| Creates global object property at top level | Usually yes | No |
| Loop iteration binding | Shared | New binding per iteration |
| Recommended for modern code | Usually no | Yes |
let vs const
| Feature | let | const |
|---|---|---|
| Reassignment | Allowed | Not allowed |
| Block scope | Yes | Yes |
| Must be initialized at declaration | No | Yes |
| Best use case | Values that change | Values that should stay assigned |
Simple rule of thumb
- Use
constif the variable should not be reassigned - Use
letif the variable will change - Avoid
varin modern JavaScript
Cheat Sheet
var x = 1;
let y = 2;
Key rules
varis function-scopedletis block-scopedvarcan be redeclared in the same scopeletcannot be redeclared in the same scopevaris hoisted and initialized toundefinedletis hoisted but stays in the temporal dead zone until declared
Scope example
if (true) {
var a = 1;
let b = 2;
}
console.log(a); // 1
console.log(b); // ReferenceError
Hoisting example
console.log(x); // undefined
var x = 5;
console.log(y); // ReferenceError
let y = 5;
Loop example
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
Best practice
- Prefer
constby default - Use
letwhen reassignment is needed - Avoid
varin new code
FAQ
Is let always better than var in JavaScript?
In modern JavaScript, yes for most cases. let is safer because it uses block scope and avoids many bugs caused by var.
Why does var print undefined before declaration?
Because var is hoisted and initialized with undefined before the code runs.
Why does let throw a ReferenceError before declaration?
Because let exists in the temporal dead zone until the declaration line is reached.
Should I still learn var?
Yes. You will still see it in older codebases, interview questions, and legacy tutorials.
What should I use in modern JavaScript: var, let, or const?
Use const by default, let when reassignment is needed, and avoid var unless you are working with older code.
Does let replace var completely?
For most modern code, yes. But understanding var is still important for reading existing JavaScript.
Can let be used inside loops safely?
Yes. In fact, it is usually better in loops because each iteration gets its own variable binding.
Mini Project
Description
Build a small loop-based logging script that shows the practical difference between var and let in asynchronous code. This project demonstrates one of the most common real-world bugs caused by var: every callback using the same final loop value.
Goal
Create a script that schedules delayed log messages and observe how var and let produce different results inside a loop.
Requirements
- Create one loop using
varand one loop usinglet - Use
setTimeoutinside both loops - Log the loop variable from each callback
- Show that
varlogs the final value repeatedly - Show that
letlogs each iteration value correctly
Keep learning
Related questions
Abort Ajax Requests with jQuery jqXHR.abort()
Learn how to cancel an in-progress jQuery Ajax request with jqXHR.abort(), handle abort status safely, and avoid stale UI updates.
Access the Correct this Inside a JavaScript Callback
Learn why JavaScript this changes in callbacks and how to preserve an object context using bind, arrow functions, and event handler patterns.
Add Key-Value Pairs to JavaScript Objects
Learn how to add key-value pairs to JavaScript objects with dot and bracket notation, dynamic keys, examples, and common mistakes.