JavaScript Closures, Scopes & Hoisting
Understanding scope, closures, and hoisting is fundamental to mastering JavaScript. These concepts are frequently tested in interviews.
Closures
A closure is a function that has access to variables in its outer (enclosing) lexical scope, even after the outer function has returned.
// Basic closure
function outer() {
const message = 'Hello';
function inner() {
console.log(message); // Accesses outer scope
}
return inner;
}
const fn = outer();
fn(); // 'Hello' - even though outer() has returned
// Counter with closure
function createCounter() {
let count = 0; // Private variable
return {
increment() {
return ++count;
},
decrement() {
return --count;
},
getCount() {
return count;
}
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getCount()); // 2
// console.log(counter.count); // undefined - private!
// Module pattern with closure
const calculator = (function() {
let result = 0; // Private state
return {
add(n) {
result += n;
return this;
},
subtract(n) {
result -= n;
return this;
},
getResult() {
return result;
},
reset() {
result = 0;
return this;
}
};
})();
calculator.add(5).add(3).subtract(2); // Chaining
console.log(calculator.getResult()); // 6
// Event handlers with closures
function createClickHandlers() {
const handlers = [];
for (let i = 0; i < 3; i++) {
handlers.push(function() {
console.log(`Button ${i} clicked`);
});
}
return handlers;
}
const handlers = createClickHandlers();
handlers[0](); // 'Button 0 clicked'
handlers[1](); // 'Button 1 clicked'
handlers[2](); // 'Button 2 clicked'Scope Types
// Global scope
var globalVar = 'I am global';
// Function scope
function myFunction() {
var functionVar = 'I am function-scoped';
console.log(globalVar); // ✅ Can access global
console.log(functionVar); // ✅ Can access function
}
// console.log(functionVar); // ❌ Error - not accessible outside
// Block scope (let & const)
if (true) {
var varVariable = 'var is function scoped';
let letVariable = 'let is block scoped';
const constVariable = 'const is block scoped';
}
console.log(varVariable); // ✅ Accessible
// console.log(letVariable); // ❌ Error
// console.log(constVariable);// ❌ Error
// Lexical scope
function outer() {
const outerVar = 'outer';
function middle() {
const middleVar = 'middle';
function inner() {
const innerVar = 'inner';
console.log(innerVar); // ✅ Own scope
console.log(middleVar); // ✅ Parent scope
console.log(outerVar); // ✅ Grandparent scope
}
inner();
}
middle();
}Hoisting
// Variable hoisting with var
console.log(x); // undefined (not error!)
var x = 5;
// Equivalent to:
// var x;
// console.log(x);
// x = 5;
// let and const are NOT hoisted to accessible state
// console.log(y); // ❌ ReferenceError: Cannot access before initialization
let y = 5;
// Function hoisting
greet(); // ✅ Works! Function declarations are hoisted
function greet() {
console.log('Hello');
}
// Function expressions are NOT hoisted
// sayHi(); // ❌ Error
const sayHi = function() {
console.log('Hi');
};
// Class hoisting - NOT hoisted
// const p = new Person(); // ❌ Error
class Person {
constructor(name) {
this.name = name;
}
}this Keyword
// Global context
console.log(this); // Window (browser) or global (Node.js)
// Object method
const person = {
name: 'John',
greet() {
console.log(this.name); // 'this' refers to person
}
};
person.greet(); // 'John'
// Losing 'this' context
const greetFn = person.greet;
greetFn(); // undefined - 'this' is now global
// Solutions:
// 1. Arrow function (lexical 'this')
const person2 = {
name: 'Jane',
greet: function() {
setTimeout(() => {
console.log(this.name); // 'this' refers to person2
}, 100);
}
};
// 2. bind()
const boundGreet = person.greet.bind(person);
boundGreet(); // 'John'
// 3. call() and apply()
const otherPerson = { name: 'Bob' };
person.greet.call(otherPerson); // 'Bob'
person.greet.apply(otherPerson); // 'Bob'
// Constructor function
function Car(make, model) {
this.make = make;
this.model = model;
}
const car = new Car('Toyota', 'Camry');
console.log(car.make); // 'Toyota'Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free