JavaScript - Closures & Scope

Overview

Estimated time: 30–40 minutes

Closures let functions remember variables from their defining scope. Use them to build private state and factories.

Learning Objectives

  • Create closures that capture state.
  • Avoid common pitfalls with loops and async callbacks.

Prerequisites

Explanation

A closure is a function that "remembers" the environment in which it was created. This means it can access variables from its outer (enclosing) scope even after that scope has finished executing. Closures are fundamental for data privacy, function factories, and callback patterns in JavaScript.

Syntax

function outer() {
  let outerVar = 'I am outside!';
  function inner() {
    console.log(outerVar); // inner has access to outerVar
  }
  return inner;
}
const fn = outer();
fn(); // 'I am outside!'

Output:

I am outside!

Example 1: Counter with Private State

function makeCounter() {
  let count = 0;
  return function() {
    count++;
    return count;
  };
}
const c1 = makeCounter();
console.log(c1()); // 1
console.log(c1()); // 2
const c2 = makeCounter();
console.log(c2()); // 1

Output:

1
2
1

Each call to makeCounter() creates a new closure with its own count variable.

Example 2: Function Factory

function makeMultiplier(factor) {
  return function(x) {
    return x * factor;
  };
}
const double = makeMultiplier(2);
const triple = makeMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15

Output:

10
15

Example 3: Closures in Loops

// Using let captures per-iteration binding
const fns = [];
for (let i = 0; i < 3; i++) {
  fns.push(() => i);
}
console.log(fns[0](), fns[1](), fns[2]()); // 0 1 2

Output:

0 1 2

If you use var instead of let, all functions would return 3 (the final value of i).

Common Pitfalls

  • Using var in loops captures a single binding; prefer let or IIFEs.
  • Unintended retention of large data via closures can cause memory leaks; null references when done.

Summary

Closures are a powerful feature in JavaScript, enabling private state, function factories, and advanced callback patterns. Mastering closures is essential for writing robust, modular, and maintainable code.

Exercises

  1. Implement once(fn) that runs a function only the first time and returns the cached result on subsequent calls.
  2. Implement a simple memoize(fn) for single-argument functions.