Frontend Dogma

On JavaScript Closures (With Examples of Interview Questions)

by @markupninja@hachyderm.io on , tagged , , , , , (share on Mastodon or on Bluesky)

A JavaScript closure (WebGlossary.info, MDN) is a function that retains access to its lexical scope even when the function is executed outside of that scope. Closures allow functions to “remember” the environment in which they were created.

It is important to understand some of their concepts:

Closures sometimes come up in web developer interviews.

What are some questions that can be asked?

“What Is a Closure in JavaScript, and Why Is It Useful?”

A closure is a function that retains access to its lexical scope, even when executed outside of that scope. Closures allow functions to “remember” variables from their outer function, making them useful for data encapsulation, maintaining state, and function factories. For example, a counter function can retain a private count variable while allowing controlled access through an inner function. This makes closures essential for writing modular and reusable code in JavaScript.

“How Do Closures Help With Data Privacy? Provide an Example”

Closures help with data privacy by restricting access to variables, ensuring they cannot be modified from outside a function. This is particularly useful for encapsulation, similar to private variables in object-oriented programming.

function createCounter() {
  let count = 0; // Private variable
  return function () {
    count++;
    return count;
  };
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2

Here, count is not accessible directly, but the returned function can increment and return its value, maintaining controlled access.

Logging I: “What Will Be Logged in the Following Code, and Why?”

function outer() {
  let count = 0;
  return function () {
    count++;
    console.log(count);
  };
}

const func1 = outer();
const func2 = outer();

func1(); // ?
func1(); // ?
func2(); // ?

The output will be:

1
2
1

Each call to outer() creates a new closure with a separate count variable. func1 and func2 do not share state because they are separate instances. The first two calls to func1() increment the first closure’s count, while func2() starts a fresh counter at 1. This demonstrates how closures create independent stateful functions.

Logging II: “What Will Be Logged in the Following Code, and in What Order?”

function makeRequest() {
  const requests = [];
  
  for (let i = 1; i <= 3; i++) {
    requests.push(() => new Promise(resolve => {
      setTimeout(() => {
        resolve(i * 2);
      }, 100 - i * 20);
    }));
  }

  return requests;
}

const requests = makeRequest();
Promise.all(requests.map(req => req())).then(console.log);

The output will be:

[2, 4, 6]

What makes this interesting:

  1. Each promise factory closes over its own i value thanks to let
  2. The timeouts are actually executed in reverse order (60ms, 40ms, 20ms)
  3. Despite different resolution times, Promise.all maintains the original array order
  4. The closure captures the multiplication operation, not just the value

In a question like this, what’s being tested is understanding of:

  1. Closure interaction with promises
  2. Promise.all behavior with array ordering
  3. setTimeout closure capturing
  4. Function factories with async operations

It’s a practical example showing how closures work with asynchronous code and promise composition.

“What Are Some Potential Pitfalls of Using Closures, and How Can They Be Avoided?”

There are at least three potential issues with closures:

  1. Memory leaks: If closures hold references to large objects, they can prevent those objects from being garbage collected, leading to an increased use of memory and risk of leaks.

  2. Unintended variable retention: Be mindful of the variables captured by closures. If not managed properly, closures can retain references to variables that are no longer needed (which can also lead to memory leaks). Release references when they are no longer necessary.

  3. Unexpected behavior in loops: Using var in loops can lead to unexpected results because var is function-scoped. It is best to use let or const within loops to ensure that each iteration captures the correct value, avoiding common pitfalls associated with closures.

—These are just a handful of many, many examples of how closures can be used in JavaScript, and what questions you could be asked. The key is to understand the concept and how it can be applied in different scenarios.

(Frontend Dogma accepts guest posts as long as they aren’t predominantly AI-generated or promotional. Although guest posts are being reviewed, Frontend Dogma cannot and does not vouch for their accuracy, and does not necessarily endorse recommendations made in them.)