(** to elevate you from junior to senior role😎**)
✅How does a closure work?
✅Can you write a function that creates a private variable using a closure?
✅Explain why the following code outputs 3 three times, and how would you fix it?
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
✅How can closures be used to create a memoization function?
✅Write a function that generates unique IDs using a closure.
✅What are the potential downsides of using closures?
✅Can you use a closure to throttle a function?
✅How would you use a closure to create a module pattern?
✅How does a closure interact with event listeners?
✅Explain the difference between a closure and a regular function.
✅How can you use a closure to create a function that only runs once?
✅Write a closure-based function to maintain a history of values.
✅How would you use a closure to create a factory function for generating customized greetings?
✅Can you explain how closures are used in currying, and provide an example?
✅How can closures cause memory leaks, and how would you prevent them?
✅Write a closure to limit the number of times a function can be called.
✅How would you use a closure to create a toggle function?
✅Can you create a closure to simulate a class-like structure without using the class keyword?
✅How do closures interact with asynchronous code, like Promises?
✅Write a closure-based function to debounce user input.
Now let's explain all of the given question one by one👋
👉1.What is a closure in JavaScript, and how does it work?
✍️A closure is a function that remembers the variables in its outer scope, even when executed outside that scope. It’s created due to JavaScript’s lexical scoping, where a function’s scope is defined by where it’s written in the code. When an inner function references outer variables, JavaScript preserves those variables in memory, forming a closure
function outer() {
let count = 0;
return function inner() {
count++;
return count;
};
}
const counter = outer();
console.log(counter()); // 1
console.log(counter()); // 2
The inner function closes over count, so even after outer finishes, count persists in memory. Each call to counter() increments it, showing how closures maintain state. This is a core concept for interviews, as it tests your grasp of scope and memory.
👉2.Can you write a function that creates a private variable using a closure?
✍️Closures are perfect for encapsulation, letting you create private variables that can’t be accessed directly. By defining a variable in an outer function and exposing methods to interact with it, you ensure data privacy.
function createPerson() {
let name = "Alice"; // Private variable
return {
getName: function() {
return name;
},
setName: function(newName) {
name = newName;
}
};
}
const person = createPerson();
console.log(person.getName()); // "Alice"
person.setName("Bob");
console.log(person.getName()); // "Bob"
console.log(person.name); // undefined
Explanation👀:
name is private because it’s only accessible through getName and setName. This pattern mimics private properties in OOP, a skill senior developers use to write secure, modular code javaScript
👉3.Explain why the following code outputs 3 three times, and how would you fix it?
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
✍️This outputs 3-three times because var is function-scoped, not block-scoped
. The setTimeout callbacks all reference the same i, which is 3 by the time the loop finishes and the callbacks run.
Fix using let:
for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
// Outputs: 0, 1, 2
Fix using IIFE (if using var):
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(function() {
console.log(j);
}, 1000);
})(i);
}
// Outputs: 0, 1, 2
Explanation👀:
Using let creates a new i for each iteration, so each closure captures the correct value. An IIFE creates a new scope per iteration, passing i as j. This question tests your understanding of scoping and closures in loops—a common interview gotcha.
👉4.How can closures be used to create a memoization function?
✍️Memoization optimizes functions by caching results for repeated inputs. A closure can maintain a private cache, storing computed values for reuse.
function memoize(fn) {
let cache = {};
return function(...args) {
let key = JSON.stringify(args);
if (key in cache) {
return cache[key];
}
cache[key] = fn(...args);
return cache[key];
};
}
function slowFib(n) {
if (n <= 1) return n;
return slowFib(n - 1) + slowFib(n - 2);
}
const memoFib = memoize(slowFib);
console.log(memoFib(10)); // 55 (computed)
console.log(memoFib(10)); // 55 (cached)
Explanation👀:
The closure over cache stores results, making subsequent calls faster. Senior developers use memoization to optimize performance, and this question tests that mindset.
👉5.Write a function that generates unique IDs using a closure.
✍️A closure can maintain a private counter to generate unique IDs, ensuring no external interference.
function createIdGenerator() {
let id = 0;
return function() {
return id++;
};
}
const generateId = createIdGenerator();
console.log(generateId()); // 0
console.log(generateId()); // 1
console.log(generateId()); // 2
Explanation👀:
The closure over id keeps it private and increments it per call. This is useful for generating keys in apps, showing practical closure application.
👉6.What are the potential downsides of using closures?
While powerful, closures have trade-offs:
Memory Usage:
Closed-over variables stay in memory until the closure is garbage-collected, potentially causing leaks if mismanaged.
Performance:
Creating many closures (e.g., in loops) can increase memory and processing overhead.
Complexity:
Overuse can make code harder to debug or maintain.
function leaky() {
let bigData = new Array(1000000).fill("data");
return () => console.log(bigData.length);
}
Here, bigData persists unnecessarily, consuming memory.
Explanation👀:
Senior developers balance closures with cleanup (like nullifying references or removing event listeners) to avoid issues, a key interview discussion point.
👉7.Can you use a closure to throttle a function?
✍️Throttling limits how often a function runs, useful for performance. A closure can track the last execution time.
function throttle(fn, delay) {
let lastCall = 0;
return function(...args) {
let now = Date.now();
if (now - lastCall >= delay) {
lastCall = now;
return fn(...args);
}
};
}
const log = () => console.log("Throttled!");
const throttledLog = throttle(log, 1000);
setInterval(throttledLog, 200);
// Logs every ~1000ms
Explanation👀:
The closure over lastCall ensures the function runs only after delay has passed. This is common in UI optimizations, showing senior-level thinking.
👉8.How would you use a closure to create a module pattern?
✍️The module pattern uses a closure to create private and public members, mimicking encapsulation.
const myModule = (function() {
let privateData = "Secret";
function privateMethod() {
return `Accessing ${privateData}`;
}
return {
publicMethod: function() {
return privateMethod();
},
publicData: "Public info"
};
})();
console.log(myModule.publicMethod()); // "Accessing Secret"
console.log(myModule.privateData); // undefined
Explanation👀:
The IIFE creates a closure, keeping privateData and privateMethod hidden. This pattern is used in libraries, testing your modular design skills.
👉9.How does a closure interact with event listeners?
✍️Closures in event listeners retain outer variables, enabling dynamic behavior in response to events.
function setupButton() {
let count = 0;
const button = document.createElement("button");
button.textContent = "Click me";
button.addEventListener("click", () => {
count++;
console.log(`Clicked ${count} times`);
});
document.body.appendChild(button);
}
setupButton();
Explane listener’s closure over count tracks clicks. Senior developers know to remove listeners to prevent leaks, a nuance interviewers may probe.
👉10. Explain the difference between a closure and a regular function.
✍️A regular function accesses its own variables and parameters but loses outer variables after their scope ends. A closure retains access to its outer scope’s variables indefinitely.
// Regular function
function add(a, b) {
return a + b;
}
console.log(add(2, 3)); // 5
// Closure
function makeAdder(x) {
return function(y) {
return x + y;
};
}
const addFive = makeAdder(5);
console.log(addFive(3)); // 8
Explanation👀:
The closure in makeAdder remembers x, unlike add, which doesn’t persist state. This tests your ability to articulate core differences.
👉11.How can you use a closure to create a function that only runs once?
✍️A closure can track execution with a flag, ensuring a function runs only once.
function createOnceRunner() {
let hasRun = false;
return function() {
if (!hasRun) {
hasRun = true;
console.log("This runs only once!");
} else {
console.log("Already ran!");
}
};
}
const runOnce = createOnceRunner();
runOnce(); // "This runs only once!"
runOnce(); // "Already ran!"
Explanation👀:
The closure over hasRun controls execution, useful for initialization tasks in app.
👉12.Write a closure-based function to maintain a history of values.
✍️A closure can store an array to track values over time, keeping it private.
function createHistoryTracker() {
let history = [];
return function(value) {
history.push(value);
return history;
};
}
const tracker = createHistoryTracker();
console.log(tracker("Step 1")); // ["Step 1"]
console.log(tracker("Step 2")); // ["Step 1", "Step 2"]
Explanation👀:
The closure over history preserves the state, a pattern for logging or undo features.
👉13.How would you use a closure to create a factory function for generating customized greetings?
✍️A closure can customize functions by retaining configuration parameters.
function createGreeter(greeting) {
return function(name) {
return `${greeting}, ${name}!`;
};
}
const sayHello = createGreeter("Hello");
console.log(sayHello("Alice")); // "Hello, Alice!"
Explanation👀:
Each returned function remembers its greeting, showing how closures enable reusable patterns.
👉14.Can you explain how closures are used in currying, and provide an example?
✍️Currying splits a multi-argument function into single-argument functions, using closures to hold intermediate values.
function curryAdd(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}
console.log(curryAdd(1)(2)(3)); // 6
Explanation👀:
Each inner function closes over outer parameters, enabling functional flexibility—a senior-level skill.
👉15.How can closures cause memory leaks, and how would you prevent them?
✍️Closures keep variables in memory, which can lead to leaks if not cleaned up, especially with large objects or event listeners.
function setupLeak() {
let heavyData = new Array(1000000).fill("data");
document.getElementById("btn").addEventListener("click", () => {
console.log(heavyData.length);
});
}
Prevention:
- Remove listeners: removeEventListener.
- Nullify references: heavyData = null.
- Use WeakMap for temporary data.
Explanation👀:
Senior developers proactively manage memory, a key interview topic.
👉16.Write a closure to limit the number of times a function can be called.
✍️A closure can track call counts to enforce a limit.
function limitCalls(fn, maxCalls) {
let calls = 0;
return function(...args) {
if (calls < maxCalls) {
calls++;
return fn(...args);
}
return "Max calls reached!";
};
}
const limitedLog = limitCalls(console.log, 2);
limitedLog("First"); // "First"
limitedLog("Second"); // "Second"
limitedLog("Third"); // "Max calls reached!"
Explanation👀:
The closure over calls ensures controlled execution, useful for APIs or trials.
👉17.How would you use a closure to create a toggle function?
✍️A closure can maintain a boolean state for toggling.
function createToggle() {
let isOn = false;
return function() {
isOn = !isOn;
return isOn ? "On" : "Off";
};
}
const toggle = createToggle();
console.log(toggle()); // "On"
console.log(toggle()); // "Off"
Explanation👀:
The closure over isOn tracks state privately, ideal for UI controls.
👉18.Can you create a closure to simulate a class-like structure without using the class keyword?
✍️A closure can encapsulate data and methods, mimicking a class.
function createStudent(name) {
let score = 0;
return {
getName: () => name,
addScore: (points) => {
score += points;
return score;
},
getScore: () => score
};
}
const student = createStudent("Alice");
console.log(student.addScore(10)); // 10
console.log(student.getScore()); // 10
Explanation👀:
The closure hides score, offering controlled access—a pre-ES6 class alternative.
👉19.How do closures interact with asynchronous code, like Promises?
✍️Closures let async operations access outer variables after delays.
function fetchWithContext(id) {
let context = `Request-${id}`;
return new Promise((resolve) => {
setTimeout(() => {
resolve(`${context}: Done`);
}, 1000);
});
}
fetchWithContext(1).then(console.log); // "Request-1: Done"
Explanation👀:
The Promise’s closure over context ensures correct data, common in API calls
👉20.Write a closure-based function to debounce user input.
✍️Debouncing delays function execution until rapid calls stop, using a closure to track timeouts.
function debounce(fn, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn(...args), delay);
};
}
const logInput = debounce((value) => console.log(`Input: ${value}`), 500);
logInput("a");
logInput("b");
setTimeout(() => logInput("c"), 200);
// Logs "Input: c" after ~500ms
Explanation👀:
The closure over timeoutId ensures only the last call runs, optimizing search or resize handlers.
Which of these closure questions challenged you the most? Or do you have a closure tip that’s helped you level up? Drop a comment below—I’d love to hear your thoughts and keep the conversation going!