JavaScript Scope & Closures
Master JavaScript scope, lexical scoping, closures, and how they work together to create powerful programming patterns
50 min•By Priygop Team•Last updated: Feb 2026
Scope Fundamentals
Scope determines the accessibility of variables and functions in different parts of your code. JavaScript uses lexical scoping, which means the scope is determined by the location of the variable declaration in the source code.
Types of Scope
- Global Scope: Variables accessible throughout the entire program
- Function Scope: Variables accessible only within a function
- Block Scope: Variables accessible only within a block (ES6+)
- Module Scope: Variables accessible only within a module
Scope Examples
Example
// Global scope
const globalVar = "I'm global";
function testFunction() {
console.log(globalVar); // Accessible
}
// Function scope
function functionScope() {
const functionVar = "I'm in function scope";
console.log(functionVar); // Accessible
function innerFunction() {
console.log(functionVar); // Accessible (closure)
const innerVar = "I'm in inner function";
console.log(innerVar); // Accessible
}
innerFunction();
// console.log(innerVar); // Error - not accessible
}
// Block scope (ES6+)
function blockScope() {
const functionVar = "Function level";
if (true) {
const blockVar = "Block level";
console.log(functionVar); // Accessible
console.log(blockVar); // Accessible
}
console.log(functionVar); // Accessible
// console.log(blockVar); // Error - not accessible
}
// Hoisting and scope
console.log(hoistedVar); // undefined (not an error)
var hoistedVar = "I'm hoisted";
// console.log(notHoisted); // Error
let notHoisted = "I'm not hoisted";
// Temporal dead zone
function temporalDeadZone() {
// console.log(tdzVar); // Error - temporal dead zone
let tdzVar = "I'm in TDZ";
console.log(tdzVar); // Works
}
// Scope chain
const globalVar2 = "Global";
function outerFunction() {
const outerVar = "Outer";
function innerFunction() {
const innerVar = "Inner";
console.log(globalVar2); // Global
console.log(outerVar); // Outer
console.log(innerVar); // Inner
}
innerFunction();
}
// Module scope (ES6 modules)
// In a separate file: utils.js
// export const API_KEY = "secret123";
// export function helper() { return "help"; }
// In main file:
// import { API_KEY, helper } from './utils.js';
// console.log(API_KEY); // Accessible
// console.log(helper()); // AccessibleClosures
Example
// Closure fundamentals
function createCounter() {
let count = 0; // Private variable
return function() {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
// Closure with parameters
function createMultiplier(factor) {
return function(number) {
return number * factor;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
// Closure with object methods
function createBankAccount(initialBalance) {
let balance = initialBalance;
return {
getBalance() {
return balance;
},
deposit(amount) {
if (amount > 0) {
balance += amount;
return `Deposited ${amount}. New balance: ${balance}`;
}
return "Invalid deposit amount";
},
withdraw(amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
return `Withdrew ${amount}. New balance: ${balance}`;
}
return "Insufficient funds or invalid amount";
}
};
}
const account = createBankAccount(1000);
console.log(account.getBalance()); // 1000
console.log(account.deposit(500)); // "Deposited 500. New balance: 1500"
console.log(account.withdraw(200)); // "Withdrew 200. New balance: 1300"
// Closure with event handlers
function createButtonHandler(buttonId) {
let clickCount = 0;
return function() {
clickCount++;
console.log(`Button ${buttonId} clicked ${clickCount} times`);
if (clickCount === 5) {
console.log(`Button ${buttonId} reached 5 clicks!`);
}
};
}
const button1Handler = createButtonHandler("btn1");
const button2Handler = createButtonHandler("btn2");
// Simulate button clicks
button1Handler(); // "Button btn1 clicked 1 times"
button1Handler(); // "Button btn1 clicked 2 times"
button2Handler(); // "Button btn2 clicked 1 times"
// Closure with async operations
function createDataFetcher(baseUrl) {
let cache = new Map();
return async function(endpoint) {
if (cache.has(endpoint)) {
console.log("Returning cached data");
return cache.get(endpoint);
}
try {
const response = await fetch(`${baseUrl}${endpoint}`);
const data = await response.json();
cache.set(endpoint, data);
console.log("Fetched and cached data");
return data;
} catch (error) {
console.error("Fetch error:", error.message);
return null;
}
};
}
// Closure with configuration
function createConfigurableLogger(level = 'info') {
const levels = { error: 0, warn: 1, info: 2, debug: 3 };
return function(message, messageLevel = 'info') {
if (levels[messageLevel] <= levels[level]) {
const timestamp = new Date().toISOString();
console.log(`[${timestamp}] [${messageLevel.toUpperCase()}] ${message}`);
}
};
}
const logger = createConfigurableLogger('warn');
logger('This is info', 'info'); // Won't log
logger('This is a warning', 'warn'); // Will log
logger('This is an error', 'error'); // Will logPractice Exercise: Scope & Closures
Example
// Exercise: Build a Module System with Closures
function createModule(name) {
let privateData = new Map();
let subscribers = new Set();
const module = {
// Public methods
set(key, value) {
privateData.set(key, value);
this.notifySubscribers('set', key, value);
},
get(key) {
return privateData.get(key);
},
delete(key) {
const deleted = privateData.delete(key);
if (deleted) {
this.notifySubscribers('delete', key);
}
return deleted;
},
clear() {
privateData.clear();
this.notifySubscribers('clear');
},
subscribe(callback) {
subscribers.add(callback);
return () => subscribers.delete(callback); // Return unsubscribe function
},
notifySubscribers(action, ...args) {
subscribers.forEach(callback => {
try {
callback(action, ...args);
} catch (error) {
console.error('Subscriber error:', error);
}
});
},
getName() {
return name;
},
getSize() {
return privateData.size;
}
};
return module;
}
// Exercise: Build a State Manager
function createStateManager(initialState = {}) {
let state = { ...initialState };
let listeners = new Set();
let middleware = [];
const stateManager = {
getState() {
return { ...state };
},
setState(newState) {
const oldState = { ...state };
state = { ...state, ...newState };
// Run middleware
middleware.forEach(middlewareFn => {
try {
middlewareFn(oldState, state);
} catch (error) {
console.error('Middleware error:', error);
}
});
// Notify listeners
listeners.forEach(listener => {
try {
listener(state, oldState);
} catch (error) {
console.error('Listener error:', error);
}
});
},
subscribe(listener) {
listeners.add(listener);
return () => listeners.delete(listener);
},
addMiddleware(middlewareFn) {
middleware.push(middlewareFn);
},
reset() {
state = { ...initialState };
listeners.forEach(listener => {
try {
listener(state, {});
} catch (error) {
console.error('Listener error:', error);
}
});
}
};
return stateManager;
}
// Exercise: Build a Function Memoizer
function createMemoizer() {
const cache = new Map();
return function(fn) {
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
console.log('Returning cached result');
return cache.get(key);
}
console.log('Computing new result');
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
};
}
// Test the exercises
const userModule = createModule('users');
const unsubscribe = userModule.subscribe((action, ...args) => {
console.log(`Module ${userModule.getName()}: ${action}`, args);
});
userModule.set('john', { name: 'John', age: 30 });
userModule.set('jane', { name: 'Jane', age: 25 });
console.log(userModule.get('john')); // { name: 'John', age: 30 }
userModule.delete('jane');
unsubscribe(); // Stop listening
const store = createStateManager({ count: 0, user: null });
store.addMiddleware((oldState, newState) => {
console.log('State changed:', { old: oldState, new: newState });
});
const unsubscribeStore = store.subscribe((newState, oldState) => {
console.log('State updated:', newState);
});
store.setState({ count: 1 });
store.setState({ user: { name: 'John' } });
unsubscribeStore();
const memoize = createMemoizer();
const expensiveFunction = memoize((n) => {
console.log('Computing...');
return n * n;
});
console.log(expensiveFunction(5)); // Computing... 25
console.log(expensiveFunction(5)); // Returning cached result 25
console.log(expensiveFunction(6)); // Computing... 36