🟨 JavaScript Q3 / 121

Explain closures in JavaScript.

AI-Powered Answer ✓ Answered

Closures are a fundamental and powerful concept in JavaScript, deeply tied to the language's lexical scoping. They allow a function to 'remember' and access variables from its outer (enclosing) scope even after the outer function has finished executing.

What is a Closure?

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In simpler terms, a closure gives you access to an outer function's scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.

How Closures Work

When an inner function is defined inside an outer function, it forms a closure. This inner function retains access to the variables and parameters of its outer (enclosing) function, even after the outer function has finished executing and returned. This is due to JavaScript's lexical scoping, meaning that the scope of a variable is determined by its location within the source code, not where it is called.

javascript
function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

const add5 = makeAdder(5);
const add10 = makeAdder(10);

console.log(add5(2));  // Outputs: 7 (5 + 2)
console.log(add10(2)); // Outputs: 12 (10 + 2)

In this example, makeAdder is the outer function, and the anonymous function function(y) { return x + y; } is the inner function. When makeAdder(5) is called, it returns a new function (assigned to add5). This new function 'remembers' the value of x (which is 5) from its lexical environment, even after makeAdder has finished executing. The same applies to add10 remembering x as 10.

Common Use Cases for Closures

  • Data Privacy / Encapsulation: Creating private variables and methods.
  • Module Pattern: Implementing modules to organize code and prevent global namespace pollution.
  • Currying: Transforming a function that takes multiple arguments into a sequence of functions, each taking a single argument.
  • Memoization: Caching the results of expensive function calls to improve performance.
  • Event Handlers and Callbacks: Preserving context for asynchronous operations.

Example: Data Privacy (Private Variables)

javascript
function createCounter() {
  let count = 0; // This variable is 'private' to the closure

  return {
    increment: function() {
      count++;
      return count;
    },
    decrement: function() {
      count--;
      return count;
    },
    getCount: function() {
      return count;
    }
  };
}

const counter = createCounter();
console.log(counter.getCount()); // Outputs: 0
console.log(counter.increment()); // Outputs: 1
console.log(counter.increment()); // Outputs: 2
console.log(counter.decrement()); // Outputs: 1
// console.log(counter.count); // Undefined - count is not directly accessible

In this createCounter example, count is a local variable within createCounter. The returned object contains methods (increment, decrement, getCount) that are closures. They all have access to the count variable from their lexical environment. However, count itself is not directly accessible from outside createCounter, effectively making it a private variable. This demonstrates data encapsulation, a core principle in object-oriented programming, achieved elegantly with closures in JavaScript.