Skip to main content
Practice Problems

Closures in JavaScript

What is a Closure?

A Closure is a function that has access to variables from its outer (parent) scope, even after that outer function has finished executing.

In Simple Terms

A closure allows a function to "remember" the environment in which it was created and use variables from that environment later.


Basic Example

javascript
function outer() { let counter = 0; // Variable from outer function function inner() { counter++; // Access to outer function variable console.log(counter); } return inner; } const increment = outer(); increment(); // 1 increment(); // 2 increment(); // 3

What happens?

  1. Function outer creates variable counter and function inner
  2. Function inner is returned and assigned to increment
  3. Although outer has finished executing, inner retains access to counter
  4. Each call to increment() uses the same variable counter

Key Point:

A closure is created automatically every time a function is created inside another function and has access to its variables.


How do Closures Work?

Closures work thanks to the Lexical Environment.

Scope Chain

javascript
let global = 'Global'; function outer() { let outerVar = 'Outer'; function inner() { let innerVar = 'Inner'; console.log(innerVar); // Access to innerVar console.log(outerVar); // Access to outerVar (closure) console.log(global); // Access to global } return inner; } const closure = outer(); closure();

closure

scope chain

Global Scope: global

Outer Scope: outerVar

Inner Scope: innerVar


Practical Examples

Counter with Private Data

javascript
function createCounter() { let count = 0; // Private variable return { increment: function() { count++; return count; }, decrement: function() { count--; return count; }, getCount: function() { return count; } }; } const counter = createCounter(); console.log(counter.increment()); // 1 console.log(counter.increment()); // 2 console.log(counter.decrement()); // 1 console.log(counter.getCount()); // 1 // No direct access to count console.log(counter.count); // undefined

Encapsulation:

Closures allow creating private variables in JavaScript that cannot be changed directly from outside.

Function Factory

javascript
function createMultiplier(multiplier) { return function(number) { return number * multiplier; }; } const double = createMultiplier(2); const triple = createMultiplier(3); console.log(double(5)); // 10 console.log(triple(5)); // 15

Each call to createMultiplier creates its own closure with its own multiplier value.

Event Listeners

javascript
function setupButtons() { const buttons = document.querySelectorAll('button'); buttons.forEach((button, index) => { button.addEventListener('click', function() { console.log(`Button ${index} clicked`); // Closure on index }); }); }

Each event handler creates a closure that remembers its index.


Classic Loop Problem

Problem

javascript
for (var i = 0; i < 3; i++) { setTimeout(function() { console.log(i); }, 1000); } // Outputs: 3, 3, 3 (not 0, 1, 2)

Why? All three functions use the same closure with one variable i. By the time setTimeout executes, the loop is finished and i = 3.

Solution 1: Use let

javascript
for (let i = 0; i < 3; i++) { setTimeout(function() { console.log(i); }, 1000); } // Outputs: 0, 1, 2

let creates a new variable on each iteration of the loop.

Solution 2: IIFE (pre-ES6)

javascript
for (var i = 0; i < 3; i++) { (function(j) { setTimeout(function() { console.log(j); }, 1000); })(i); } // Outputs: 0, 1, 2

IIFE creates a new scope with its own copy of i.


Patterns with Closures

Module Pattern

javascript
const Calculator = (function() { // Private variables and functions let result = 0; function log(operation, value) { console.log(`${operation}: ${value}, result = ${result}`); } // Public API return { add: function(value) { result += value; log('Add', value); return this; }, subtract: function(value) { result -= value; log('Subtract', value); return this; }, getResult: function() { return result; } }; })(); Calculator .add(10) .add(5) .subtract(3); console.log(Calculator.getResult()); // 12

Memoization

javascript
function memoize(fn) { const cache = {}; // Private cache return function(...args) { const key = JSON.stringify(args); if (key in cache) { console.log('From cache'); return cache[key]; } console.log('Computing'); const result = fn(...args); cache[key] = result; return result; }; } const expensiveOperation = memoize((n) => { let sum = 0; for (let i = 0; i < n; i++) { sum += i; } return sum; }); console.log(expensiveOperation(1000000)); // Computing console.log(expensiveOperation(1000000)); // From cache

Currying

javascript
function curry(fn) { return function curried(...args) { if (args.length >= fn.length) { return fn.apply(this, args); } return function(...nextArgs) { return curried.apply(this, args.concat(nextArgs)); }; }; } const sum = (a, b, c) => a + b + c; const curriedSum = curry(sum); console.log(curriedSum(1)(2)(3)); // 6 console.log(curriedSum(1, 2)(3)); // 6 console.log(curriedSum(1)(2, 3)); // 6

React and Closures

"Stale Closure" Problem

javascript
function Counter() { const [count, setCount] = useState(0); useEffect(() => { const timer = setInterval(() => { // Will always use initial value count = 0 setCount(count + 1); }, 1000); return () => clearInterval(timer); }, []); // Empty dependency array return <div>{count}</div>; }

Solution

javascript
function Counter() { const [count, setCount] = useState(0); useEffect(() => { const timer = setInterval(() => { // Uses current value setCount(prevCount => prevCount + 1); }, 1000); return () => clearInterval(timer); }, []); return <div>{count}</div>; }

Memory Leaks

Closures can lead to memory leaks if they hold large objects.

Memory Leak

javascript
function createHeavyClosure() { const hugeArray = new Array(1000000).fill('data'); return function() { console.log('Hello'); // Function doesn't use hugeArray, but it stays in memory }; } const fn = createHeavyClosure();

Solution

javascript
function createHeavyClosure() { const hugeArray = new Array(1000000).fill('data'); // Use only what's needed const dataLength = hugeArray.length; return function() { console.log('Array length:', dataLength); // hugeArray can be garbage collected }; }

Frequently Asked Questions

What will this code output?

javascript
function createFunctions() { const functions = []; for (var i = 0; i < 3; i++) { functions.push(function() { console.log(i); }); } return functions; } const funcs = createFunctions(); funcs[0](); // ? funcs[1](); // ? funcs[2](); // ?

Answer: All three calls will output 3, because all functions close over the same variable i, which equals 3 after the loop.


Conclusion

Closures are:

  • Function + its lexical environment
  • Access to outer function variables after its completion
  • Foundation for modules, currying, memoization
  • Way to create private data
  • Potential source of memory leaks
  • Cause of "stale" values in React hooks

In Interviews:

Be prepared to:

  • Explain what a closure is in simple terms
  • Provide practical use case examples
  • Solve problems with loops and setTimeout
  • Explain memory leak issues
  • Show how closures work in React

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?
Practice Problems