Compile Ready
Module 3 · Scope & Closures

Closures

Intermediate9m read5m practice14m total
ClosuresLexical EnvironmentPrivate StateInterview Classic

Introduction

A closure is a function bundled with references to variables from its lexical environment. In plain English: an inner function can keep using variables from an outer function even after the outer function has returned.

If Module 3 has one flagship concept, this is it. Closures power callbacks, data privacy, function factories, memoization, debouncing, and many of the most common JavaScript interview questions.

Why This Matters

Closures are one of the most-tested JavaScript topics at Microsoft, Google, Amazon, Meta, Netflix, and Apple. Interviewers expect you to define them crisply, trace output questions, explain private state, and fix the var loop callback bug. A strong closure explanation signals that your mental model is deeper than syntax.

Theory

Interview-ready definition

A closure is a function together with the lexical environment it was created in, allowing the function to access outer variables even after the outer function has finished executing.

A concise interview answer: A closure is a function that remembers variables from the scope where it was created.

The mental model

When JavaScript creates a function, it records a hidden link to the surrounding lexical environment. If that function is returned, stored in an array, passed as a callback, or called later, the link remains. The outer variables it can still reach stay alive as long as the closure is reachable.

Important nuance: closures capture bindings, not frozen snapshots. If multiple closures point at the same binding, they see the same changing value. If a loop creates a fresh binding per iteration with let, each closure gets a different binding.

The counter factory

The classic example is function makeCounter(){ let c=0; return function(){ return ++c; }; }. Each call to makeCounter creates a new lexical environment with its own c. The returned function closes over that c, so it can increment it later. Two counters are independent because they were created by two different calls.

Private state

Closures can hide variables from the outside world. If balance is local to a factory function and only returned methods can reach it, outside code cannot read or assign balance directly. This is the old-school JavaScript route to encapsulation and still appears in interviews.

The classic var loop bug

With var, a loop has one shared function-scoped binding. Every callback closes over that same binding, so after the loop ends they all see the final value. Fix it by using let (fresh per-iteration binding) or by wrapping each iteration in an IIFE that captures the current value as an argument.

PatternWhat the closure remembersResult
Counter factoryone c per factory callindependent counters
Private modulehidden local variablescontrolled access
for (var i...) callbacksone shared iall see final value
for (let i...) callbacksone i per iterationeach sees its own value

Visual Diagrams

A closure keeps an environment alive
makeCounter() call
  creates environment E1
  c = 0
      |
      v
  returns inner function
      |
      v
counter variable in outer code -----> function object
                                      hidden link -----> E1 { c }

Calling counter() later follows the hidden link and updates c.

The outer call is gone from the stack, but the captured environment remains reachable through the returned function.

Code Examples

Counter factory: each call gets private state

This is the closure example every JavaScript interviewer expects you to know.

Loading…

Private variables through closures

balance is not a property on the returned object. Only the returned methods can access it.

Loading…

Fixing `var` loop closures with `let` or an IIFE

Both fixes work because they create a separate binding for each callback to close over.

Loading…

Playground

Loading editor…
Console

Press Run to execute the code and see output here.

Output Prediction

Predict the output #1

javascript
1function makeCounter() {
2 let c = 0;
3 return function () {
4 return ++c;
5 };
6}
7
8const a = makeCounter();
9const b = makeCounter();
10
11console.log(a());
12console.log(a());
13console.log(b());
14console.log(a());

Predict the output #2

javascript
1const fns = [];
2
3for (var i = 0; i < 2; i++) {
4 fns.push(function () {
5 return i;
6 });
7}
8
9for (let j = 0; j < 2; j++) {
10 fns.push(function () {
11 return j;
12 });
13}
14
15console.log(fns[0](), fns[1](), fns[2](), fns[3]());

Coding Exercises

Build a private counter object

Medium

Implement createCounter(initial) that returns an object with methods increment(), decrement(), get(), and reset(). The current count must be private: outside code should not be able to read or write it as a property on the returned object.

Interview Questions

1Define a closure in JavaScript. Give a crisp answer an interviewer would accept.

A closure is a function bundled with its lexical environment. It lets the function access variables from the scope where it was created, even after that outer scope has finished executing. Example: a makeCounter function returns an inner function that keeps accessing and updating the outer c variable.

Asked at:MicrosoftGoogleAmazonMetaNetflixApple

Follow-ups

  • Do closures capture values or bindings?
  • Why are two counters independent?
2How would you explain a closure to a junior developer?

I would say: imagine a function carries a backpack. When it is created, it puts the outer variables it needs into that backpack by reference. Later, even if the outer function is done, the inner function still has the backpack, so it can keep using those variables. That is why a counter function can remember its count between calls.

Follow-ups

  • What is the limitation of the backpack analogy?
  • Why do multiple closures sometimes share the same variable?
3How do closures provide private variables?

Declare the variable inside a factory function and return functions that operate on it. The variable is not exposed as an object property or global name, so outside code cannot access it directly. Only the returned closures can read or modify it.

Quiz

1. What does a closure remember?

2. What is printed? `function outer(){ let x = 0; return [function(){ x++; return x; }, function(){ x++; return x; }]; } const pair = outer(); console.log(pair[0]()); console.log(pair[1]());`

3. What is printed? `const f=[]; for(var i=0;i<2;i++){ f.push(function(){ return i; }); } for(let j=0;j<2;j++){ f.push(function(){ return j; }); } console.log(f[0](), f[1](), f[2](), f[3]());`

Summary

  • A closure is a function plus access to variables from its lexical environment.
  • Closures capture bindings, so shared bindings show updated values, not frozen snapshots.
  • Each factory call creates a new environment, which is why two counters are independent.
  • Closures enable private state, callbacks, factories, and fixes for classic loop problems.

Cheat Sheet

Definition: a closure is a function bundled with its lexical environment.

Mantra: functions remember where they were created, not where they are called.

Counter: makeCounter returns a function that keeps using c; each factory call gets a new c.

Bindings, not snapshots: multiple closures can share one binding; let loop iterations create fresh bindings.

Private state: keep variables local to a factory and expose methods that close over them.

Loop bug: var = one shared i; fix with let or an IIFE.