Closures

JavaScript ES2024 · ✓ verified by execution on 2026-08-15

A closure is one of the most powerful and frequently misunderstood concepts in JavaScript. Simply put, a closure is a function bundled together with its lexical environment. This means that an inner function always has access to the variables and parameters of its outer function, even after the outer function has finished executing.

Whenever you create a function in JavaScript, a closure is created at that exact moment. The function permanently captures a reference to the environment where it was born.

Lexical Environment let x = 1; Inner Function return x; Execution Context f(); // evaluates to 1
A closure maintains a live reference to its outer environment, even when passed somewhere else.

Accessing Outer Scope

Because of closures, an inner function gives you access to an outer function’s scope. Here is a basic example where the inner function displayName accesses the variable name defined in its parent function makeFunc.

javascript · visualize
function makeFunc() {
  const name = 'Mozilla';
  function displayName() {
    console.log(name);
  }
  return displayName;
}
const myFunc = makeFunc();
myFunc();
Mozilla

When myFunc is invoked, it still has access to name, even though makeFunc has already finished executing.

Emulating Private Methods

JavaScript historically didn’t have a native way to declare private methods. However, closures provide a clean way to emulate private state. The variables captured by the closure cannot be modified from the outside.

javascript · visualize
const counter = (function() {
  let privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment() {
      changeBy(1);
    },
    value() {
      return privateCounter;
    }
  };
})();
counter.increment();
console.log(counter.value());
1

The Scope Chain

Every closure actually has three distinct scopes it can pull from:

  1. Local scope (its own scope)
  2. Enclosing scope (can be block, function, or module scope)
  3. Global scope
javascript
const globalVar = 'G';
function outer() {
  const outerVar = 'O';
  return function inner() {
    const innerVar = 'I';
    console.log(globalVar, outerVar, innerVar);
  }
}
outer()();
Output
G O I

The Loop Closure Trap

A very common edge case occurs when creating closures inside loops using var. Because closures capture variables by reference and not by value, and because var is function-scoped rather than block-scoped, all closures created inside the loop will point to the exact same variable. By the time they execute, the loop has already finished, and they all see the final value.

Predict the output javascript

Read the code. What exactly will it print? Commit to an answer before you look.

function createFuncs() {
  var arr = [];
  for (var i = 0; i < 3; i++) {
    arr.push(function() {
      console.log(i);
    });
  }
  return arr;
}
const funcs = createFuncs();
funcs[0]();
funcs[1]();
funcs[2]();
Output
3
3
3

You might have expected this to print 0, 1, 2. Instead, because i was declared with var, there is only one i variable shared across all three closures. By the time the functions are called, the loop has finished and i has reached 3.

Using let instead of var fixes this entirely, because let creates a new block-scoped binding for every single iteration of the loop!

Edge Cases to Watch Out For

Check yourself

What exactly does a closure capture?

Reveal answer

A live reference to the outer variable, reacting to changes. — A closure captures a reference to the variable, so if the variable changes, the closure sees the new value.

When are closures created in JavaScript?

Reveal answer

Every time any function is created. — Closures are created every time any function is created. All functions in JavaScript form closures over their lexical environment.

How do closures affect memory?

Reveal answer

They can cause memory leaks if the closure is retained unnecessarily, keeping the outer scope alive. — Closures maintain a live reference to their lexical environment, which prevents garbage collection of those variables and can cause memory leaks if not managed carefully.

Which variables does a closure have access to?

Reveal answer

Its local scope, the enclosing scope, and the global scope. — Every closure has access to its own local scope, the enclosing scope (which can be nested functions or blocks), and the global scope.

Challenges

Challenge 1 +20 XP

Create a function `createGreeting(greeting)` that returns a new function. The returned function should take a `name` and return a string combining the greeting and the name.

javascript
  • Test 1 — expects "Hello Alice"
Need a hint? (−25% XP)

Return an inner function that takes `name` as an argument and combines it with `greeting`.

Show solution (0 XP)
function createGreeting(greeting) {
  return function(name) {
    return greeting + ' ' + name;
  }
}

const sayHello = createGreeting('Hello');
console.log(sayHello('Alice'));

🐞 Bug Hunt +50 XP

This code tries to create a private secret using a closure, but it has a bug where the secret doesn't update. Fix the bug.

This code runs. It just does the wrong thing. Read it, find the defect, fix it — the tests below decide when you are right.

javascript
  • Test 1 — expects "abc"
Need a hint? (−25% XP)

Look closely at the `setSecret` function. Does it update the outer `secret` variable or create a new local one?

Show solution (0 XP)
function createSecret(secret) {
  return {
    getSecret: () => secret,
    setSecret: (newSecret) => {
      secret = newSecret;
    }
  };
}

const s = createSecret('xyz');
s.setSecret('abc');
console.log(s.getSecret());