Skip to main content
Course/Module 3/Topic 2 of 3Intermediate

JavaScript Scope & Closures

Master JavaScript scope, lexical scoping, closures, and how they work together to create powerful programming patterns

50 minBy Priygop TeamLast 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()); // Accessible

Closures

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 log

Practice 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
Chat on WhatsApp
Priygop - Leading Professional Development Platform | Expert Courses & Interview Prep