Ways to declare a variable: var, let, const
JavaScript has three ways to declare a variable: var, let and const. They differ in scope, in whether they can be redeclared, and in how they behave under hoisting. Let us go through them in detail.
Theory
TL;DR
var, function scoped, allows redeclaration, hoists with the valueundefined.let, block scoped, the value can be reassigned, redeclaring it in the same block is not allowed.const, block scoped, cannot be reassigned, but the contents of an object or array can still change.letandconstare hoisted too, yet before their declaration line they sit in the temporal dead zone and throw aReferenceError.- The modern standard:
constby default,letwhen needed,varnot at all.
Quick example
function demo() {
if (true) {
var fromVar = 'visible in the whole function';
let fromLet = 'visible only inside this block';
}
console.log(fromVar); // 'visible in the whole function'
console.log(fromLet); // ReferenceError: fromLet is not defined
}1. var, the old way (ES5 and earlier)
var name = 'Tim';Details:
-
It can be redeclared and reassigned:
javascriptvar x = 10; var x = 20; // does not raise an error -
Function scope (it ignores
{}blocks):javascriptif (true) { var a = 5; } console.log(a); // 5, the variable "escaped" the block -
It is hoisted, so the variable is reachable before its declaration, but its value is
undefined:javascriptconsole.log(user); // undefined var user = 'Alex';
Using
varin modern code is not recommended.
2. let, the modern way for values that change
let age = 25;
age = 26; // reassignment is allowedDetails:
-
The value can be changed, but the name cannot be redeclared in the same block:
javascriptlet a = 1; // let a = 2; SyntaxError -
Block scope:
javascriptif (true) { let x = 10; } console.log(x); // ReferenceError, the variable is not visible outside the block -
It is unreachable before its declaration, unlike
var:javascriptconsole.log(a); // ReferenceError let a = 5;
Use
letwhen the value of the variable is going to change.
3. const, a constant (an immutable binding)
const PI = 3.14;Details:
-
It cannot be reassigned or redeclared:
javascriptconst x = 10; x = 20; // TypeError -
But if it holds an object or an array, the data inside can still be changed:
javascriptconst user = { name: 'Tim' }; user.name = 'Alex'; // allowed // user = {} not allowed, the binding itself cannot be reassigned -
Block scope, same as
let.
Use
constby default, andletonly if the variable really has to change. When you need genuine immutability of an object, applyObject.freeze().
Hoisting and the temporal dead zone
All three declarations are hoisted; the difference is what happens before the declaration line. var is initialised with undefined while the scope is being created, so reading it does not fail and quietly returns undefined, which is a source of hard to catch bugs. let and const reserve the name as well but leave it uninitialised: the gap between the start of the block and the declaration line is called the temporal dead zone (TDZ), and any access inside it throws a ReferenceError.
console.log(withVar); // undefined, quiet and dangerous
var withVar = 1;
console.log(withLet); // ReferenceError: Cannot access 'withLet' before initialization
let withLet = 1;A second practical difference shows up in loops: let creates a fresh binding on every iteration, while var has a single one for the whole function.
for (var i = 0; i < 3; i++) setTimeout(() => console.log(i)); // 3 3 3
for (let j = 0; j < 3; j++) setTimeout(() => console.log(j)); // 0 1 2Quick comparison
| Feature | var | let | const |
|---|---|---|---|
| Scope | Function | Block | Block |
| Can be redeclared | yes | no | no |
| Can be reassigned | yes | yes | no |
| Hoisting | Yes, with the value undefined | Yes, but unusable before the declaration | Yes, but unusable before the declaration |
| Modern standard | Obsolete | Yes | Yes |
Common mistakes
- Believing that
constmakes an object immutable. Only the binding is frozen:user.name = 'Alex'works, whileuser = {}throws aTypeError. - Declaring a
constwithout a value.const x;is aSyntaxError, the value is required right away. - Relying on
varhoisting and reading the variable before its declaration, gettingundefinedinstead of an error. - Using
varin a loop with asynchronous callbacks and then wondering why every callback sees the final counter value. - Picking
letwhere the value never changes. It hides intent:consttells the reader immediately that no reassignment is coming.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.