Understanding var, let and const in JavaScript
SkillVeris Team
Engineering Team

In modern JavaScript you declare variables with const by default, switch to let only when the value must be reassigned, and avoid var entirely.
In this guide, you'll learn:
- const and let are block-scoped, while var is function-scoped — the single biggest source of surprising bugs in older code.
- const prevents reassignment of the binding, but objects and arrays it points to can still be mutated.
- var declarations are hoisted and initialised as undefined; let and const are hoisted but sit in the temporal dead zone until declared.
- Choosing const first makes code easier to reason about because a reader knows the binding will never change.
1var, let, and const at a Glance
JavaScript gives you three ways to declare a variable: var, let, and const. The short answer is to use const for values that never get reassigned, let for values that do, and to stop using var in new code entirely. That single rule covers the vast majority of real-world decisions.
The reason the three keywords exist is history. var came first and has quirky scoping and hoisting behaviour. let and const arrived with ES6 in 2015 to fix those quirks, giving you predictable block scoping and clearer intent. Understanding the differences turns a confusing topic into a habit you apply without thinking.
2Reassignment: The First Question to Ask
The most practical difference is whether you can reassign the variable. A const binding cannot be pointed at a new value after it is created, while let and var can. This is why const should be your default — most variables never actually need to change, and locking that in prevents accidental overwrites.
- const total = 100 # reassigning total later throws a TypeError
- let count = 0 # count = count + 1 is allowed
- var name = 'Ada' # reassignment allowed, but avoid var
- const user = { name: 'Ada' } # user.name = 'Grace' still works — see the mutation note
💡Pro Tip
Reach for const first. If the linter or your logic tells you the value must change, only then switch to let. You will find const covers far more cases than beginners expect.
3Scope: Block vs Function
Scope decides where a variable is visible. let and const are block-scoped, meaning they only exist inside the nearest pair of curly braces — an if statement, a for loop, or any { } block. var is function-scoped: it ignores blocks and is visible throughout the entire function that contains it.
This difference matters most in loops and conditionals. A var declared inside a for loop leaks out and remains accessible after the loop ends, which frequently causes bugs. A let stays neatly contained where you declared it.
The Classic Loop Bug
A common interview example is a loop that sets timeouts. With var, every callback shares one variable and prints the final value. With let, each iteration gets its own binding and the callbacks print the expected values.
for (var i = 0; i < 3; i++) setTimeout(() => console.log(i)) # prints 3, 3, 3
for (let i = 0; i < 3; i++) setTimeout(() => console.log(i)) # prints 0, 1, 24Hoisting and the Temporal Dead Zone
Hoisting is JavaScript moving declarations to the top of their scope before code runs. All three keywords are hoisted, but they behave differently. A var is initialised to undefined immediately, so reading it before its line gives undefined rather than an error.
let and const are hoisted too, but they are not initialised. From the top of the block until the declaration line, they sit in the temporal dead zone, and touching them throws a ReferenceError. This is intentional — it catches the mistake of using a variable before you meant to define it.
🔑Why This Helps
The temporal dead zone turns a silent undefined bug into a loud, immediate error, which is exactly what you want while debugging.
5const Does Not Mean Immutable
A frequent misunderstanding is that const makes a value unchangeable. It does not. const only prevents reassignment of the binding — the label — not mutation of the object or array it references. You can push to a const array or edit properties on a const object freely.
If you genuinely need an object that cannot be changed, reach for Object.freeze, which makes its top-level properties read-only. For deeper immutability, teams often use libraries or copy-on-write patterns instead of relying on const alone.
- const list = [] # list.push(1) works fine
- list = [1] # this line throws — reassignment is blocked
- const config = Object.freeze({ debug: true }) # config.debug = false is ignored
6How to Choose in Practice
A simple decision order keeps your code consistent and readable. Most experienced developers follow the same short mental checklist for every declaration they write.
- Start with const — assume the value will not be reassigned.
- Switch to let only when you clearly need to reassign, such as a counter or accumulator.
- Never introduce var in new code; its scoping causes more problems than it solves.
- When editing old code, prefer replacing var with let or const as you go.
- Name variables for what they hold, not for the keyword — clarity beats cleverness.
7Common Mistakes to Avoid
A handful of recurring errors trip up people learning the difference between these keywords. Knowing them in advance saves hours of confused debugging.
- Assuming const means the object is frozen — it only locks the binding, not the contents.
- Using var inside loops and being surprised when the variable leaks outside.
- Reading a let or const before its declaration and hitting a temporal-dead-zone ReferenceError.
- Declaring everything as let out of habit, which hides which values are meant to be stable.
- Redeclaring the same var twice in one scope without noticing — let and const would have flagged it.
⚠️Watch Out
Mixing var and let in the same function makes scope hard to trace. Pick block-scoped declarations everywhere and the mental model stays simple.
8Key Takeaways
The decision comes down to a few durable rules you can apply on autopilot.
- Default to const; use let only for values that must be reassigned; avoid var.
- let and const are block-scoped; var is function-scoped and leaks out of blocks.
- All declarations hoist, but let and const stay in the temporal dead zone until declared.
- const blocks reassignment but still allows mutation of objects and arrays.
- Refactoring var to let or const is a safe, high-value cleanup in older code.
9Frequently Asked Questions
Q: Should I ever use var in 2026? A: In new code, no. let and const cover every case with clearer scoping. You will still encounter var in legacy codebases and some minified output, so it is worth understanding, but there is no reason to write it yourself.
Q: Can I reassign a const? A: No. Attempting to reassign a const binding throws a TypeError at runtime. You can, however, mutate the object or array that a const points to, since only the binding is protected.
Q: What is the temporal dead zone? A: It is the span between the start of a block and the line where a let or const is declared. Accessing the variable during that span throws a ReferenceError, which helps catch use-before-declaration bugs early.
Q: Is const faster than let? A: Performance differences are negligible in practice. Choose based on intent and readability — const signals a stable value — rather than on any micro-optimisation.
Related Reading
Get The Print Version
Download a PDF of this article for offline reading.
About the Publisher
SkillVeris Team
Engineering Team
Our engineering writers turn abstract code concepts into hands-on, project-driven learning experiences.
View all postsRelated Posts
Never miss an update
Get the latest tutorials and guides delivered to your inbox.
No spam. Unsubscribe anytime.