Variables & Scope
Variables are containers for storing data values. Understanding variable declarations and scope is fundamental to writing clean, bug-free JavaScript code.
📌 Variable Declarations
1. var (Old Way - Avoid)
// Function-scoped, can be redeclared
var name = 'John';
var name = 'Jane'; // No error, but problematic
// Hoisted to top of function
console.log(x); // undefined (not an error!)
var x = 5;
// Function scope
function test() {
var y = 10;
if (true) {
var y = 20; // Same variable!
console.log(y); // 20
}
console.log(y); // 20 (not 10!)
}
2. let (Modern - Block Scoped)
// Block-scoped, cannot be redeclared
let age = 25;
// let age = 30; // Error: Cannot redeclare
// Can be reassigned
age = 26; // OK
// Block scope
if (true) {
let message = 'Hello';
console.log(message); // 'Hello'
}
// console.log(message); // Error: message is not defined
// Loop scope
for (let i = 0; i < 3; i++) {
console.log(i); // 0, 1, 2
}
// console.log(i); // Error: i is not defined
// Not hoisted (Temporal Dead Zone)
// console.log(name); // Error: Cannot access before initialization
let name = 'John';
3. const (Modern - Constant)
// Block-scoped, cannot be reassigned
const PI = 3.14159;
// PI = 3.14; // Error: Assignment to constant variable
// Must be initialized
// const x; // Error: Missing initializer
// Block scope
const API_URL = 'https://api.example.com';
// Objects and arrays can be mutated
const person = { name: 'John' };
person.name = 'Jane'; // OK - mutating property
person.age = 30; // OK - adding property
// person = {}; // Error - reassigning variable
const numbers = [1, 2, 3];
numbers.push(4); // OK - mutating array
numbers[0] = 10; // OK - changing element
// numbers = []; // Error - reassigning variable
Best Practices
- Use const by default - Prevents accidental reassignment
- Use let when you need to reassign values
- Avoid var - Use let/const instead for predictable behavior
- Declare at the top of their scope for clarity
🎯 Scope
Global Scope
// Variables declared outside functions
const globalVar = 'I am global';
function test() {
console.log(globalVar); // Accessible everywhere
}
test();
console.log(globalVar); // Also accessible here
// In browsers, global variables attach to window
var oldGlobal = 'attached to window';
console.log(window.oldGlobal); // Works
// let and const don't attach to window
let newGlobal = 'not on window';
console.log(window.newGlobal); // undefined
Function Scope
function myFunction() {
const functionScoped = 'Only inside function';
console.log(functionScoped); // Works
function innerFunction() {
console.log(functionScoped); // Can access parent scope
}
innerFunction();
}
myFunction();
// console.log(functionScoped); // Error: Not accessible outside
// Each function has its own scope
function outer() {
const x = 10;
function inner() {
const x = 20; // Different variable
console.log(x); // 20
}
inner();
console.log(x); // 10
}
Block Scope
// let and const are block-scoped
{
const blockVar = 'inside block';
console.log(blockVar); // Works
}
// console.log(blockVar); // Error
// if statements
if (true) {
let message = 'Hello';
const value = 42;
console.log(message, value); // Works
}
// console.log(message); // Error
// for loops
for (let i = 0; i < 3; i++) {
const squared = i * i;
console.log(squared);
}
// console.log(i); // Error
// console.log(squared); // Error
// switch statements
switch (true) {
case true:
let caseVar = 'case 1';
console.log(caseVar);
break;
}
// console.log(caseVar); // Error
Lexical Scope (Closures)
// Inner functions access outer scope
function outer() {
const outerVar = 'I am outer';
function inner() {
console.log(outerVar); // Can access outer variable
}
return inner;
}
const myFunction = outer();
myFunction(); // 'I am outer' - closure in action!
// Practical closure example
function createCounter() {
let count = 0; // Private variable
return {
increment() {
count++;
return count;
},
decrement() {
count--;
return count;
},
getCount() {
return count;
}
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.decrement()); // 1
console.log(counter.getCount()); // 1
// console.log(count); // Error: count is private
🏗️ Hoisting
Variable Hoisting
// var is hoisted
console.log(x); // undefined (not an error)
var x = 5;
// Equivalent to:
var x;
console.log(x); // undefined
x = 5;
// let and const are NOT hoisted (Temporal Dead Zone)
// console.log(y); // Error: Cannot access before initialization
let y = 10;
// console.log(z); // Error: Cannot access before initialization
const z = 15;
Function Hoisting
// Function declarations are hoisted
sayHello(); // Works! 'Hello'
function sayHello() {
console.log('Hello');
}
// Function expressions are NOT hoisted
// greet(); // Error: greet is not a function
const greet = function() {
console.log('Hi');
};
// Arrow functions are NOT hoisted
// welcome(); // Error: welcome is not a function
const welcome = () => {
console.log('Welcome');
};
🎨 Naming Conventions
// camelCase for variables and functions (standard)
let userName = 'John';
let totalAmount = 100;
function calculateTotal() {}
// PascalCase for classes and constructors
class UserAccount {}
function Person() {}
// UPPER_SNAKE_CASE for constants
const MAX_SIZE = 100;
const API_KEY = 'abc123';
const DATABASE_URL = 'mongodb://localhost';
// Valid names
let name = 'John';
let _private = 'internal';
let $jquery = 'selector';
let name2 = 'test';
// Invalid names (syntax errors)
// let 2name = 'test'; // Cannot start with number
// let my-name = 'test'; // Hyphens not allowed
// let my name = 'test'; // Spaces not allowed
// let class = 'test'; // Reserved keyword
// Descriptive names (best practice)
// Good
let userAge = 25;
let isLoggedIn = true;
let totalPrice = 99.99;
// Bad
let x = 25;
let flag = true;
let temp = 99.99;
🔄 Variable Assignment Patterns
Multiple Declarations
// Separate declarations (recommended)
let firstName = 'John';
let lastName = 'Doe';
let age = 30;
// Single statement (valid but less readable)
let a = 1, b = 2, c = 3;
// Multiple const
const PI = 3.14159,
E = 2.71828,
PHI = 1.61803;
Destructuring Assignment
// Array destructuring
const colors = ['red', 'green', 'blue'];
const [first, second, third] = colors;
console.log(first); // 'red'
console.log(second); // 'green'
// Object destructuring
const user = {
name: 'John',
age: 30,
city: 'New York'
};
const { name, age, city } = user;
console.log(name); // 'John'
console.log(age); // 30
// With different variable names
const { name: userName, age: userAge } = user;
console.log(userName); // 'John'
// Default values
const { country = 'USA' } = user;
console.log(country); // 'USA' (default)
Swap Variables
// Old way (using temp variable)
let a = 1;
let b = 2;
let temp = a;
a = b;
b = temp;
console.log(a, b); // 2, 1
// Modern way (destructuring)
let x = 10;
let y = 20;
[x, y] = [y, x];
console.log(x, y); // 20, 10
🛡️ Common Pitfalls
// 1. Forgetting to declare (creates global)
function test() {
// x = 5; // BAD: Creates global variable
let x = 5; // GOOD: Local variable
}
// 2. var in loops
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Prints: 3, 3, 3 (not 0, 1, 2!)
// Fix with let
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Prints: 0, 1, 2
// 3. Const doesn't make objects immutable
const obj = { name: 'John' };
obj.name = 'Jane'; // Allowed!
// obj = {}; // Error
// To make immutable:
const frozen = Object.freeze({ name: 'John' });
// frozen.name = 'Jane'; // Silently fails (error in strict mode)
// 4. Temporal Dead Zone
{
// console.log(myVar); // Error: TDZ
let myVar = 5;
}
// 5. Redeclaring with let
let value = 10;
// let value = 20; // Error: Already declared
💡 Practical Examples
Counter with Closure
function createCounter(initialValue = 0) {
let count = initialValue;
return {
increment(amount = 1) {
count += amount;
return count;
},
decrement(amount = 1) {
count -= amount;
return count;
},
reset() {
count = initialValue;
return count;
},
getValue() {
return count;
}
};
}
const counter = createCounter(10);
console.log(counter.increment()); // 11
console.log(counter.increment(5)); // 16
console.log(counter.decrement(3)); // 13
console.log(counter.reset()); // 10
Configuration Object
// Using const for configuration
const config = {
apiUrl: 'https://api.example.com',
timeout: 5000,
retries: 3,
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
}
};
// Can modify properties
config.timeout = 10000;
// Object.freeze for immutable config
const immutableConfig = Object.freeze({
version: '1.0.0',
environment: 'production'
});
// immutableConfig.version = '2.0.0'; // Silently fails
Module Pattern
const calculator = (function() {
// Private variables
let result = 0;
// Private function
function log(operation, value) {
console.log(`${operation}: ${value}`);
}
// Public API
return {
add(num) {
result += num;
log('Added', num);
return this;
},
subtract(num) {
result -= num;
log('Subtracted', num);
return this;
},
multiply(num) {
result *= num;
log('Multiplied', num);
return this;
},
getResult() {
return result;
},
reset() {
result = 0;
return this;
}
};
})();
// Usage
calculator.add(10).multiply(2).subtract(5);
console.log(calculator.getResult()); // 15
🎯 Key Takeaways
- const: Use by default for variables that won't be reassigned
- let: Use when you need to reassign values, block-scoped
- var: Avoid in modern JavaScript, function-scoped and hoisted
- Block scope: Variables declared with let/const are only accessible within their block
- Closures: Inner functions can access variables from outer functions
- Hoisting: var declarations are hoisted, let/const have Temporal Dead Zone
- Naming: Use camelCase for variables, UPPER_CASE for constants
- Immutability: const prevents reassignment but not object/array mutation