Declaring Variables
A variable is a named container for storing a value. JavaScript has three keywords for declaring variables:
let, const, and var. Modern JavaScript uses let and
const exclusively - var is legacy.
const name = "Alice"; // cannot be reassigned
let age = 30; // can be reassigned
var city = "London"; // legacy - avoid in modern code
// Declaration without initialization (let only)
let score;
console.log(score); // undefined
// Multiple declarations
let x = 1, y = 2, z = 3;
let
let declares a block-scoped variable that can be reassigned.
Use it when the variable's value will change during execution.
let count = 0;
count = count + 1; // reassignment OK
count++; // also OK
console.log(count); // 2
// let is block-scoped
if (true) {
let insideBlock = "only here";
console.log(insideBlock); // "only here"
}
// console.log(insideBlock); // ReferenceError - outside block
// Cannot re-declare in the same scope
let user = "Alice";
// let user = "Bob"; // SyntaxError: already declared
// But can shadow in a nested scope
let x = 1;
{
let x = 2; // different variable, shadows outer x
console.log(x); // 2
}
console.log(x); // 1
const
const declares a block-scoped binding that cannot be reassigned.
It must be initialized when declared. Use const by default - it signals the value
will not change.
const PI = 3.14159;
// PI = 3; // TypeError: Assignment to constant variable
// const with objects - binding is const, content is mutable
const user = { name: "Alice", age: 30 };
user.age = 31; // OK - mutating the object
user.city = "Paris"; // OK - adding a property
console.log(user); // { name: 'Alice', age: 31, city: 'Paris' }
// user = {}; // TypeError - cannot reassign the binding
// const with arrays
const colors = ["red", "green"];
colors.push("blue"); // OK - mutating the array
console.log(colors); // ['red', 'green', 'blue']
// To truly freeze: use Object.freeze()
const frozen = Object.freeze({ x: 1, y: 2 });
frozen.x = 99; // silently ignored (or TypeError in strict mode)
console.log(frozen); // { x: 1, y: 2 }
var (Legacy - Avoid)
var is the original variable declaration keyword. It has two major problems compared
to let/const: it is function-scoped (not block-scoped)
and it is hoisted with an initial value of undefined.
// var leaks out of blocks (not block-scoped)
if (true) {
var leaked = "I escape the block!";
}
console.log(leaked); // "I escape the block!" - surprise!
// var allows re-declaration (silent bug source)
var x = 1;
var x = 2; // no error - just overwrites
console.log(x); // 2
// var is hoisted with undefined
console.log(y); // undefined (not ReferenceError!)
var y = 5;
// Same behavior as:
var y; // hoisted to top of function
console.log(y); // undefined
y = 5;
The function-scope and hoisting behavior of var has caused countless bugs over the years. Always use const or let. You will still see var in older tutorials and legacy codebases - understanding it helps you read that code.
Scope Rules
Scope determines where a variable is accessible. JavaScript has three types:
// 1. Global scope - accessible everywhere
const globalVar = "I am global";
function example() {
// 2. Function scope - accessible inside function only
let funcVar = "I am local";
if (true) {
// 3. Block scope (let/const only)
let blockVar = "I am block-scoped";
console.log(globalVar); // OK - outer scope visible
console.log(funcVar); // OK - same function
console.log(blockVar); // OK - same block
}
// console.log(blockVar); // ReferenceError
}
example();
// console.log(funcVar); // ReferenceError
Hoisting
JavaScript hoists declarations - moves them to the top of their scope during
the compilation phase. var declarations are hoisted and initialized to
undefined. let and const are hoisted but NOT initialized.
// var hoisting - initialized to undefined
console.log(a); // undefined (not ReferenceError)
var a = 5;
// let/const hoisting - in TDZ until declaration
// console.log(b); // ReferenceError: Cannot access 'b' before init
let b = 10;
// Function declarations are fully hoisted
greet(); // Works! Function is fully hoisted
function greet() {
console.log("Hello!");
}
// Function expressions are NOT fully hoisted
// sayHi(); // TypeError: sayHi is not a function
const sayHi = function() { console.log("Hi!"); };
Temporal Dead Zone (TDZ)
The Temporal Dead Zone is the period from the start of a block scope to the
point where a let or const variable is declared. Accessing it in
this zone throws a ReferenceError:
{
// --- TDZ starts here for 'x' ---
console.log(typeof x); // ReferenceError (not "undefined"!)
// --- TDZ ends here ---
let x = 5;
console.log(x); // 5 - now safe to access
}
// Why TDZ exists: prevents using variables before they are set.
// With var this was a silent bug: undefined when you expected a value.
// With let/const it is an explicit error: you see the problem immediately.
Naming Best Practices
// Use descriptive names - explain intent
const d = new Date(); // bad
const currentDate = new Date(); // good
// camelCase for variables and functions
let firstName = "Alice";
let isLoggedIn = false; // boolean: use is/has/can prefix
function getUserById(id) {}
// SCREAMING_SNAKE_CASE for true constants
const MAX_LOGIN_ATTEMPTS = 5;
const API_BASE_URL = "https://api.example.com";
// Avoid single letters (except loop counters)
for (let i = 0; i < 10; i++) {} // i is conventional in loops
// Avoid abbreviations that are unclear
const usr = {}; // bad
const user = {}; // good
let vs const vs var - Quick Reference
| Feature | const | let | var |
|---|---|---|---|
| Scope | Block | Block | Function |
| Reassignable | No | Yes | Yes |
| Re-declarable | No | No | Yes |
| Hoisting | Yes (TDZ) | Yes (TDZ) | Yes (undefined) |
| Must initialize | Yes | No | No |
| Use in new code | Default | When needed | Never |