Scope and Closures
Understanding Scope
Scope defines where a variable is visible and accessible in your code. JavaScript has three levels of scope: global, function, and block.
Global Scope
A variable declared outside any function or block is in the global scope. It is accessible from anywhere in the script:
const appVersion = "2.0"; // Global scope
function showVersion() {
console.log(appVersion); // Accessible here
}
showVersion(); // "2.0"
console.log(appVersion); // "2.0"
Be careful with global variables. They can be read and modified from anywhere, which makes bugs harder to trace. Minimise globals in production code.
Function Scope
Variables declared inside a function (with var, let, or const) are only accessible within that function:
function calculateDiscount(price) {
const discountRate = 0.1; // Function-scoped
return price * (1 - discountRate);
}
console.log(calculateDiscount(100)); // 90
// console.log(discountRate); // ReferenceError: not accessible outside
Block Scope
Variables declared with let or const inside a block ({}) are only accessible within that block:
if (true) {
let blockMessage = "I am block-scoped";
const alsoBlock = "Me too";
var leaky = "I am NOT block-scoped"; // var ignores block scope!
}
// console.log(blockMessage); // ReferenceError
// console.log(alsoBlock); // ReferenceError
console.log(leaky); // "I am NOT block-scoped" — var leaks out of blocks
This is one of the main reasons to avoid var: it leaks out of if blocks and for loops.
Lexical Scope
JavaScript uses lexical scope (also called static scope): a function can access variables from the scope where it was defined, not where it is called.
const language = "English";
function outer() {
const dialect = "British";
function inner() {
// inner can see both language (global) and dialect (outer's scope)
console.log(language); // "English"
console.log(dialect); // "British"
}
inner();
}
outer();
Closures
A closure is a function that "remembers" the variables from its outer scope even after the outer function has finished executing. Closures are one of the most powerful and widely used features in JavaScript.
function makeCounter() {
let count = 0; // This variable lives in the closure
return function() {
count++;
return count;
};
}
const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
// count is not accessible from outside
// console.log(count); // ReferenceError
Even after makeCounter has returned, the inner function retains access to count. This is a closure.
Practical Uses of Closures
Private Data
Closures let you create variables that are private — accessible only through a controlled interface:
function createWallet(initialBalance) {
let balance = initialBalance; // Private — not directly accessible
return {
deposit(amount) {
balance += amount;
return balance;
},
withdraw(amount) {
if (amount > balance) {
return "Insufficient funds";
}
balance -= amount;
return balance;
},
getBalance() {
return balance;
}
};
}
const wallet = createWallet(100);
console.log(wallet.deposit(50)); // 150
console.log(wallet.withdraw(30)); // 120
console.log(wallet.getBalance()); // 120
// console.log(wallet.balance); // undefined — balance is private
Memoisation
Memoisation caches the results of expensive function calls so they are not recalculated:
function memoize(fn) {
const cache = {};
return function(n) {
if (cache[n] !== undefined) {
console.log("From cache:", n);
return cache[n];
}
cache[n] = fn(n);
return cache[n];
};
}
const slowSquare = (n) => n * n;
const fastSquare = memoize(slowSquare);
console.log(fastSquare(5)); // Calculates: 25
console.log(fastSquare(5)); // From cache: 25
Key Takeaways
- Global scope is accessible everywhere; minimise global variables to avoid naming conflicts
- Function scope restricts variables to the function where they are declared
- Block scope restricts let/const to the block where they are declared; var ignores block scope
- Lexical scope means a function accesses variables from where it was written, not where it is called
- A closure is a function that retains access to its outer scope's variables after the outer function has returned
- Closures enable private data patterns and memoisation — both are common in real-world JavaScript
Practice Exercise
- Write a function
makeGreeter(greeting)that returns a function. The returned function should take a name and return the greeting with the name. Example:makeGreeter("Hello")("Amara")returns"Hello, Amara!" - Write a
createCounter(start, step)function that returns an object withincrement,decrement, andvaluemethods. Use a closure to keep the count private. - Predict the output of this code before running it:
let x = 10;
function outer() {
let x = 20;
function inner() {
let x = 30;
console.log(x);
}
inner();
console.log(x);
}
outer();
console.log(x);
Try it yourself
Key Takeaways
- Global scope makes variables accessible everywhere; use it sparingly to avoid naming conflicts
- Function scope restricts variables to the function where they are declared
- let and const respect block scope; var does not and leaks out of if blocks and loops
- Lexical scope means a function accesses variables from the scope where it was written, not where it is called
- A closure is formed when an inner function retains access to outer scope variables after the outer function returns
- Closures enable private data, factory functions, and memoisation — patterns that appear constantly in real JavaScript codebases
Quick Quiz
1.What is a closure in JavaScript?
2.Why is var considered problematic with block scope?
3.What does lexical scope mean?
4.What will the following code output? let count = 0; function inc() { count++; } inc(); inc(); console.log(count);
Ready to go further?
CareerEx gives you structured 12-week training, live classes every Saturday and Sunday, real tutor feedback, and a certificate. Join the next cohort.
Join CareerEx