Photo by Christian Joudrey on Unsplash
Day 1 - 100 JS Interview Questions : Closures
Interviewer : What are closures, and how do they work in JavaScript?
PermalinkBasic Understanding
Imagine a backpack you carry to school..
The backpack is like the outer function, which holds supplies (variables).
Inside the backpack, there’s a notebook (closure) that keeps notes for your math class.
Even when you leave school and go home, the notebook in your backpack still has the math notes, allowing you to study later.
Similarly, in JavaScript, a closure carries the variables it needs (like the math notes) even after the outer function (school) is finished.
At its core, a closure is a feature in JavaScript that allows a function to "remember" the environment in which it was created, even after the outer function has finished executing. This means that a closure gives access to an outer function's scope from within an inner function.
PermalinkSimple Example
function outerFunction() {
let outerVariable = "I'm from the outer function!";
function innerFunction() {
console.log(outerVariable); // Accessing outerVariable from the inner function
}
return innerFunction;
}
const myClosure = outerFunction(); // outerFunction returns innerFunction
myClosure(); // Logs: "I'm from the outer function!"
Here, outerFunction
creates a local variable outerVariable
. innerFunction
is defined inside outerFunction
and accesses outerVariable
. The fun thing is even after outerFunction
has finished running, innerFunction
still remembers and can access outerVariable
. That’s a closure!
PermalinkStep by Step Execution
Step 1 : Function Creation - When outerFunction
is called, the JavaScript engine creates a scope for it. This scope contains variables like outerVariable
.
Step 2 : Returning the Inner Function - outerFunction
returns innerFunction
, but importantly, the scope (and variables) of outerFunction
are not discarded when outerFunction
finishes.
Step 3 : Closure Formation - When innerFunction
is called later (via myClosure()
), it has access to the scope of outerFunction
, even though outerFunction
has already completed. This is because the JavaScript engine keeps a reference to the outer scope in memory due to the closure.
PermalinkA Problem Statement
What does this code print?
function outerFunction() {
let counter = 0;
return function innerFunction() {
console.log(counter);
counter++;
};
}
const func1 = outerFunction();
const func2 = outerFunction();
func1(); // ? __
func1(); // ? __
func2(); // ? __
func2(); // ? __
Take a guess…
func1
andfunc2
create independent closures with separatecounter
variables.Output:
func1(): 0
func1(): 1
func2(): 0
func2(): 1
outerFunction
creates a new closure with its own counter
.PermalinkThe Other Side
Lets have a look at one typical use case regarding this: Memory Management
Closures can sometimes hold onto memory unnecessarily, causing memory leaks. For example:
function largeClosure() {
const bigArray = new Array(1000000).fill("data");
return function smallFunction() {
console.log("Don't Look at Me!!, I don't use bigArray");
};
}
const closure = largeClosure();
Here, bigArray
remains in memory because of the closure, even though smallFunction
doesn’t use it. To avoid this, minimize unnecessary variables in closures.
PermalinkApplications
Another common question to be anticipated is tell me about real life applications of Closures. Basically a question related to real life applications can be rare, yet it is best to be prepare for them at any stage.
One of the most common application of closures is Creating Private Variables. In JavaScript, closures help simulate private variables, as JavaScript does not have native private variables like some other languages.
function bankAccount(initialBalance) {
let balance = initialBalance;
return {
deposit: function (amount) {
balance += amount;
console.log(`Deposited: $${amount}. Balance: $${balance}`);
},
withdraw: function (amount) {
if (amount > balance) {
console.log("Insufficient funds!");
} else {
balance -= amount;
console.log(`Withdrew: $${amount}. Balance: $${balance}`);
}
},
};
}
const account = bankAccount(100);
account.deposit(50); // Deposited: $50. Balance: $150
account.withdraw(30); // Withdrew: $30. Balance: $120
Take a note that the balance
variable is hidden from direct access. Only the returned deposit
and withdraw
methods can interact with balance
.
Secondly, Closures are essential in handling events.
function attachEventHandlers() {
for (let i = 1; i <= 3; i++) {
document.getElementById(`button${i}`).addEventListener("click", function () {
console.log(`Button ${i} clicked!`);
});
}
}
attachEventHandlers();
Here, closures let each button "remember" its unique i
value even after the loop finishes.
PermalinkChapter Check-Up
Predict the output of the following code:
function createFunctions() {
let arr = [];
for (var i = 0; i < 3; i++) {
arr.push(function () {
return i;
});
}
return arr;
}
const funcs = createFunctions();
console.log(funcs[0]()); // __ ?
console.log(funcs[1]()); // __ ?
console.log(funcs[2]()); // __ ?
Create a Timer: Write a function that returns another function to count seconds from 0. Each time the returned function is called, it should increment the count.
Private Counter: Implement a counter function where you cannot directly access or modify the count variable, except through specific methods.
PermalinkConclusion
A closure is like carrying a "notebook" (inner function) with notes (variables) from the classroom (outer function), even when you're home (the outer function has ended). Closures are powerful for protecting data from outside interference and creating flexible, reusable code for tasks like counters, event listeners, and encapsulation. They also come with responsibilities, like avoiding unnecessary memory usage.
Subscribe to our newsletter
Read articles from JavaScript Interview Prep directly inside your inbox. Subscribe to the newsletter, and don't miss out.