Frontend interview answer

Explain Closures in JavaScript

HighIntermediateJavascript

Interview quick answer

Closures keep outer variables alive after the outer function returns. The real frontend debug value is explaining stale async callbacks, loop-capture bugs, and long-lived listeners that keep old objects alive in memory.

Interview focus

This JavaScript interview question tests whether you can explain JavaScript closures: stale callback bugs, loop capture, and memory retention, connect it to production trade-offs, and handle common follow-up questions.

  • JavaScript closures: stale callback bugs, loop capture, and memory retention explanation without falling back to memorized definitions
  • Closure and Functions reasoning, edge cases, and production failure modes
  • How you would answer the most likely JavaScript interview follow-up
Practice more JavaScript interview questions
Interview answer drill

Use this JavaScript interview question to rehearse a quick answer, common mistake, follow-up, and production pitfall.

Full interview answer

The Core Idea

A closure means a function remembers variables from the place where it was created, not from where it is called. That powers private state and factories, but the real production/debug value is knowing which bugs closures create: async callbacks reading stale values, loop handlers capturing one shared binding, and long-lived listeners keeping old objects alive longer than expected.

JAVASCRIPT
// Simple example
function makeCounter() {
  let count = 0; // stays in memory
  return function () {
    count++;
    return count;
  };
}

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

// The inner function still 'remembers' count
                  

Why It’s Useful

Closures are powerful because they let you:

  • Keep variables private (not accessible globally)
  • Store state between function calls
  • Create helpers like once(), memoize(), and other reusable patterns
JAVASCRIPT
// Example: once()
function once(fn) {
  let called = false;
  let value;
  return function (...args) {
    if (!called) {
      called = true;
      value = fn.apply(this, args);
    }
    return value;
  };
}

const init = once(() => console.log('Initialized'));
init(); // Logs once
init(); // Does nothing — remembers state
                  

Common Mistake

Using var in loops creates one shared variable for all iterations — so all functions close over the same value:

JAVASCRIPT
const funcs = [];
for (var i = 0; i < 3; i++) {
  funcs.push(() => i);
}
console.log(funcs.map(fn => fn())); // [3, 3, 3]
                  

Fixes:

  • Use let for block scoping
  • Or use an IIFE (immediately invoked function) to capture a copy
JAVASCRIPT
// Fix with let
for (let j = 0; j < 3; j++) {
  setTimeout(() => console.log(j), 0);
}
// 0 1 2
                  

Worked debug example: shared binding vs fresh closure

A common frontend bug is wiring delayed handlers inside a loop and then discovering every callback points at the last row. Closures are not broken here; the code created one shared binding. The fix is to capture a fresh binding per iteration or create a factory that receives the current value.

JAVASCRIPT
// Broken: one shared `var` binding
const saveHandlers = [];
for (var row = 0; row < 3; row++) {
  saveHandlers.push(() => console.log('save row', row));
}
saveHandlers[0](); // save row 3

// Fixed: each closure gets its own binding
const fixedHandlers = [];
for (let row = 0; row < 3; row++) {
  fixedHandlers.push(() => console.log('save row', row));
}
fixedHandlers[0](); // save row 0
                  

Memory Notes

Closures keep referenced values alive as long as the inner function is still reachable. If a long-lived event listener or timer still closes over a large cache, form snapshot, or DOM node, that old object graph stays in memory until you remove the listener or clear the reference.

Still so complicated?

Imagine you leave a room (outer function ends), but you gave your friend (inner function) a copy of the key to your drawer (variables). Even though you’re gone, they can still open it — that’s a closure!

Summary

  • Functions automatically form closures in JavaScript.
  • Inner functions remember outer variables (their lexical scope).
  • Used for data privacy, persistence, and modular code.
  • Use let/const to avoid loop bugs.
  • Don’t overuse closures in long-lived objects to prevent memory leaks.
Similar questions
Guides
Preparing for interviews?