JavaScript: let, const, and var - Complete Study Notes
Overview
JavaScript provides three keywords for declaring variables:
var - The old way (ES5 and before)
let - Modern, block-scoped variable (ES6+)
const - Modern, block-scoped constant (ES6+)
1. Global Scope
Definition: Variables declared outside any function or block are in the global scope and accessible everywhere.
var in Global Scope
javascript
var globalVar = "I'm global with var";
console.log(globalVar); // Output: I'm global with var
function test() {
console.log(globalVar); // Output: I'm global with var
}
test();
let in Global Scope
javascript
let globalLet = "I'm global with let";
console.log(globalLet); // Output: I'm global with let
function test() {
console.log(globalLet); // Output: I'm global with let
}
test();
const in Global Scope
javascript
const globalConst = "I'm global with const";
console.log(globalConst); // Output: I'm global with const
function test() {
console.log(globalConst); // Output: I'm global with const
}
test();
Key Point: All three work in global scope, but var attaches to the window object in browsers, while let and const do not.
javascript
var varGlobal = "var";
let letGlobal = "let";
const constGlobal = "const";
console.log(window.varGlobal); // Output: "var"
console.log(window.letGlobal); // Output: undefined
console.log(window.constGlobal); // Output: undefined
2. Function Scope
Definition: Variables declared inside a function are only accessible within that function.
var is Function-Scoped
javascript
function myFunction() {
var functionVar = "I'm inside a function";
console.log(functionVar); // Output: I'm inside a function
}
myFunction();
console.log(functionVar); // ReferenceError: functionVar is not defined
let is Also Function-Scoped
javascript
function myFunction() {
let functionLet = "I'm inside a function";
console.log(functionLet); // Output: I'm inside a function
}
myFunction();
console.log(functionLet); // ReferenceError: functionLet is not defined
const is Also Function-Scoped
javascript
function myFunction() {
const functionConst = "I'm inside a function";
console.log(functionConst); // Output: I'm inside a function
}
myFunction();
console.log(functionConst); // ReferenceError: functionConst is not defined
Key Point: All three keywords respect function scope - they cannot be accessed outside the function they're declared in.
3. Block Scope
Definition: A block is code within curly braces {} (if statements, loops, etc.). Block scope means the variable is only accessible within
that block.
var is NOT Block-Scoped (This is the problem!)
javascript
if (true) {
var blockVar = "I escape the block!";
}
console.log(blockVar); // Output: I escape the block!
// var ignores block scope - this can cause bugs!
for (var i = 0; i < 3; i++) {
// loop code
}
console.log(i); // Output: 3
// i leaks out of the loop!
let IS Block-Scoped
javascript
if (true) {
let blockLet = "I'm trapped in the block";
}
console.log(blockLet); // ReferenceError: blockLet is not defined
for (let j = 0; j < 3; j++) {
// loop code
}
console.log(j); // ReferenceError: j is not defined
// j stays inside the loop - much safer!
const IS Block-Scoped
javascript
if (true) {
const blockConst = "I'm trapped in the block";
}
console.log(blockConst); // ReferenceError: blockConst is not defined
for (const k = 0; k < 3; k++) { // Note: This will error in loop
// This doesn't work because k++ tries to reassign const
}
// TypeError: Assignment to constant variable
Key Point: let and const are block-scoped, var is not. This is why let and const are preferred in modern JavaScript.
4. Reassignment
Definition: Changing the value of a variable after it's been declared.
var Allows Reassignment
javascript
var x = 10;
console.log(x); // Output: 10
x = 20;
console.log(x); // Output: 20
// No problem!
let Allows Reassignment
javascript
let y = 10;
console.log(y); // Output: 10
y = 20;
console.log(y); // Output: 20
// Works perfectly!
const Does NOT Allow Reassignment
javascript
const z = 10;
console.log(z); // Output: 10
z = 20; // TypeError: Assignment to constant variable
Important Exception: With const, you can't reassign the variable, but you CAN modify object properties or array elements!
javascript
const person = { name: "Alice" };
person.name = "Bob"; // This works!
console.log(person.name); // Output: Bob
const numbers = [1, 2, 3];
numbers.push(4); // This works!
console.log(numbers); // Output: [1, 2, 3, 4]
// But you can't reassign the entire object/array:
person = { name: "Charlie" }; // TypeError: Assignment to constant variable
numbers = [5, 6, 7]; // TypeError: Assignment to constant variable
5. Redeclaration
Definition: Declaring a variable with the same name again in the same scope.
var Allows Redeclaration (Dangerous!)
javascript
var name = "Alice";
var name = "Bob"; // No error - var allows this
console.log(name); // Output: Bob
// This can accidentally overwrite variables:
var count = 5;
// ... 100 lines of code later ...
var count = 10; // Oops! Accidentally redeclared
console.log(count); // Output: 10
let Does NOT Allow Redeclaration
javascript
let age = 25;
let age = 30; // SyntaxError: Identifier 'age' has already been declared
// But you can reassign:
let age = 25;
age = 30; // This is fine
console.log(age); // Output: 30
const Does NOT Allow Redeclaration
javascript
const PI = 3.14;
const PI = 3.14159; // SyntaxError: Identifier 'PI' has already been declared
Key Point: var allows dangerous redeclarations, let and const protect you from this mistake.
6. Initialization
Definition: Giving a variable a value when you declare it.
var Can Be Declared Without Initialization
javascript
var a;
console.log(a); // Output: undefined
a = 10;
console.log(a); // Output: 10
let Can Be Declared Without Initialization
javascript
let b;
console.log(b); // Output: undefined
b = 20;
console.log(b); // Output: 20
const MUST Be Initialized
javascript
const c; // SyntaxError: Missing initializer in const declaration
// Must do this:
const c = 30;
console.log(c); // Output: 30
Key Point: const requires immediate initialization because you can't reassign it later.
7. Temporal Dead Zone (TDZ)
Definition: The time between entering a scope and the variable's declaration where accessing the variable causes a ReferenceError.
var Has NO Temporal Dead Zone
javascript
console.log(myVar); // Output: undefined (due to hoisting)
var myVar = 10;
console.log(myVar); // Output: 10
let Has a Temporal Dead Zone
javascript
console.log(myLet); // ReferenceError: Cannot access 'myLet' before initialization
let myLet = 20;
console.log(myLet); // Output: 20
// TDZ example in a block:
{
// TDZ starts here for 'temp'
console.log(temp); // ReferenceError
let temp = 5; // TDZ ends here
console.log(temp); // Output: 5
}
const Has a Temporal Dead Zone
javascript
console.log(myConst); // ReferenceError: Cannot access 'myConst' before initialization
const myConst = 30;
console.log(myConst); // Output: 30
Key Point: let and const have a TDZ to prevent accessing variables before they're initialized. This catches more bugs at development
time.
8. Hoisting Behavior
Definition: JavaScript moves declarations to the top of their scope during compilation. However, the behavior differs for var, let, and
const.
var is Hoisted and Initialized with undefined
javascript
console.log(hoistedVar); // Output: undefined
var hoistedVar = "I'm hoisted!";
console.log(hoistedVar); // Output: I'm hoisted!
// What JavaScript actually does:
// var hoistedVar = undefined; (hoisted)
// console.log(hoistedVar);
// hoistedVar = "I'm hoisted!";
// console.log(hoistedVar);
Impact: You can access var variables before declaration (they'll be undefined), which can lead to confusing bugs.
javascript
function example() {
console.log(x); // Output: undefined (not ReferenceError!)
var x = 5;
console.log(x); // Output: 5
}
example();
let is Hoisted but NOT Initialized (TDZ)
javascript
console.log(hoistedLet); // ReferenceError: Cannot access 'hoistedLet' before initialization
let hoistedLet = "I'm hoisted but in TDZ!";
console.log(hoistedLet); // Output: I'm hoisted but in TDZ!
// What JavaScript actually does:
// let hoistedLet; (hoisted but in TDZ - cannot access)
// console.log(hoistedLet); // ReferenceError
// hoistedLet = "I'm hoisted but in TDZ!";
// console.log(hoistedLet);
Impact: Accessing let before declaration throws an error, forcing you to write cleaner code.
javascript
function example() {
console.log(y); // ReferenceError: Cannot access 'y' before initialization
let y = 5;
console.log(y);
}
example();
const is Hoisted but NOT Initialized (TDZ)
javascript
console.log(hoistedConst); // ReferenceError: Cannot access 'hoistedConst' before initialization
const hoistedConst = "I'm hoisted but in TDZ!";
console.log(hoistedConst); // Output: I'm hoisted but in TDZ!
Impact: Same as let - forces declaration before use.
javascript
function example() {
console.log(z); // ReferenceError: Cannot access 'z' before initialization
const z = 5;
console.log(z);
}
example();
Hoisting in Functions
javascript
// var function hoisting example
function testVar() {
console.log(item); // Output: undefined
if (false) {
var item = "test"; // Hoisted to function scope!
}
console.log(item); // Output: undefined
}
testVar();
// let function hoisting example
function testLet() {
console.log(item); // ReferenceError: item is not defined
if (false) {
let item = "test"; // Not hoisted out of block!
}
console.log(item); // ReferenceError: item is not defined
}
testLet();
Common Pitfalls and Best Practices
Pitfall 1: var in Loops
javascript
// Problem with var:
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // Output: 3, 3, 3 (all reference same i)
}, 100);
}
// Solution with let:
for (let j = 0; j < 3; j++) {
setTimeout(() => {
console.log(j); // Output: 0, 1, 2 (each has own j)
}, 100);
}
Pitfall 2: Accidental Global Variables
javascript
function oops() {
// Forgot var/let/const:
accidentalGlobal = "I'm global now!";
}
oops();
console.log(accidentalGlobal); // Output: I'm global now!
// Always use let/const to avoid this!
Best Practices
1. Default to const: Use const by default for values that won't change
2. Use let when reassignment is needed: Only use let when you need to reassign
3. Avoid var: There's almost no reason to use var in modern JavaScript
4. Declare at the top: Declare variables at the top of their scope for clarity
javascript
// Good practice example:
function calculateTotal(price, quantity) {
const TAX_RATE = 0.1; // Won't change - use const
let total = price * quantity; // Will change - use let
total = total + (total * TAX_RATE);
return total;
}
Summary Comparison Table
Feature var let const
Scope Function-scoped Block-scoped Block-scoped
Hoisting Yes, initialized as undefined Yes, but in TDZ (not accessible) Yes, but in TDZ (not accessible)
Temporal Dead Zone No Yes Yes
Redeclaration ✅ Allowed ❌ Not allowed ❌ Not allowed
Reassignment ✅ Allowed ✅ Allowed ❌ Not allowed
Initialization Required ❌
No ❌ No ✅ Yes
Global Object Property Yes (becomes window.varName) No No
Use in Loops ⚠️ Problematic (shared reference) ✅ Works well (block-scoped) ⚠️ Limited (can't reassign)
Best Practice ❌ Avoid in modern code ✅ Use when reassignment needed ✅ Use by default
Quick Decision Guide
Choose const when:
The value won't be reassigned
Declaring objects or arrays (even if you'll modify properties/elements)
Declaring constants (PI, MAX_SIZE, etc.)
You want maximum safety and clarity
Choose let when:
The value will be reassigned
Loop counters (for, while loops)
Variables that change through program execution
Accumulator variables
Choose var when:
You're working with legacy code that requires it
Otherwise, DON'T use var in modern JavaScript!
Final Notes
Modern JavaScript (ES6+) introduced let and const to fix the problems with var
Block scoping and the Temporal Dead Zone help catch errors early
Using const by default makes code easier to understand and debug
Always declare variables - never create accidental globals
The hoisting behavior of var is a common source of bugs - avoid it!