Question

How JavaScript Closures Work: A Beginner-Friendly Guide

javascriptfunctionvariablesscopeclosures

Question

How can JavaScript closures be explained to someone who already understands functions, variables, and related basics, but does not yet understand what closures are?

I have seen the Scheme example on Wikipedia, but it did not make the idea clear for me.

Short Answer

By the end of this page, you will understand what a JavaScript closure is, why it happens automatically, how inner functions remember variables from their outer scope, and where closures are useful in real code such as event handlers, factories, callbacks, and data privacy patterns.

Concept

A closure happens when a function remembers variables from the place where it was created, even after that outer code has finished running.

In JavaScript, functions do not only carry their code. They also keep access to the lexical scope in which they were defined. That means an inner function can still use variables from an outer function later.

Here is the key idea:

function outer() {
  let count = 0;

  function inner() {
    count++;
    return count;
  }

  return inner;
}

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

Why does this work?

  • outer() runs and creates count
  • inner() is defined inside outer()
  • outer() returns inner
  • Even after outer() finishes, inner() still remembers count

That remembered connection between the function and its outer variables is the closure.

Why closures matter

Closures are useful because they let you:

  • Preserve state between function calls
  • Hide data from the outside world
  • Create configurable functions
  • Write cleaner callbacks and event handlers
  • Build factories and modules

Closures are a normal part of JavaScript, not a special syntax feature. They happen naturally whenever a function uses variables from an outer scope.

Mental Model

Think of a closure like a backpack that a function carries around.

  • The function is the person
  • The outer variables are items placed into the backpack
  • When the function goes elsewhere, it still has access to those items

Example:

function makeGreeter(name) {
  return function () {
    console.log(`Hello, ${name}!`);
  };
}

const greetAlice = makeGreeter("Alice");
greetAlice(); // Hello, Alice!

Even though makeGreeter has already finished, the returned function still has name in its backpack.

Another useful way to think about it:

  • A variable is not copied into the function as plain text
  • The function keeps access to the actual variable from its original scope
  • If that variable changes, the closure sees the updated value
Take Quiz

Syntax and Examples

Basic syntax

A closure usually appears when:

  1. A function is created inside another function
  2. The inner function uses a variable from the outer function
  3. The inner function is used later
function outer() {
  let message = "Hello";

  function inner() {
    console.log(message);
  }

  return inner;
}

const fn = outer();
fn(); // Hello

Example: private counter

function createCounter() {
  let count = 0;

  return function () {
    count += 1;
    return count;
  };
}

const counterA = createCounter();
const counterB = createCounter();

console.log(counterA()); // 1
console.log(counterA()); // 2
console.log(counterB()); // 1

Explanation

  • Each call to createCounter() creates a new count
  • The returned function closes over that specific count
  • counterA and counterB do not share the same state

Example: configuration function

function multiplyBy(multiplier) {
  return function (number) {
    return number * multiplier;
  };
}

const double = multiplyBy(2);
const triple = multiplyBy(3);

console.log(double(5)); // 10
console.log(triple(5)); // 15

This is useful when you want to create specialized functions from a more general one.

Step by Step Execution

Consider this example:

function makeCounter() {
  let count = 0;

  return function () {
    count = count + 1;
    return count;
  };
}

const counter = makeCounter();
console.log(counter());
console.log(counter());

Step-by-step

1. makeCounter is defined

At this point, nothing runs yet. JavaScript only stores the function definition.

2. makeCounter() is called

const counter = makeCounter();

Inside this call:

  • A new local variable count is created with value 0
  • A new inner function is created
  • That inner function uses count
  • The inner function is returned

Now counter refers to the returned function.

3. Why count still exists

Normally, local variables disappear after a function finishes. But here, the returned function still needs count. So JavaScript keeps that variable alive because the closure refers to it.

4. First call to counter()

console.log(counter());

The inner function runs:

  • Reads count which is 0
  • Adds 1
  • Stores 1 back into count
  • Returns 1

Output:

1

5. Second call to counter()

The same closure runs again:

  • Reads count which is now 1
  • Adds 1
  • Stores 2
  • Returns 2

Output:

2

Important observation

The function does not get a fresh count every time counter() runs. It keeps using the remembered variable created when makeCounter() was called.

Real World Use Cases

Closures are common in real JavaScript programs.

1. Event handlers

function setupButton(message) {
  return function () {
    console.log(message);
  };
}

const handleClick = setupButton("Button clicked");

The event handler remembers the message it was created with.

2. Timers and callbacks

function delayedGreeting(name) {
  setTimeout(function () {
    console.log(`Hello, ${name}`);
  }, 1000);
}

The callback remembers name even though it runs later.

3. Data privacy

function createBankAccount() {
  let balance = 0;

  return {
    deposit(amount) {
      balance += amount;
    },
    getBalance() {
      return balance;
    }
  };
}

balance cannot be changed directly from outside.

4. Function factories

function makeFormatter(prefix) {
  return function (value) {
    return `${prefix}: ${value}`;
  };
}

Useful for creating reusable, customized logic.

5. Memoization and caching

A function can remember earlier results in a private variable to avoid repeated work.

Take Quiz

Real Codebase Usage

In real projects, closures are often used as small building blocks rather than as isolated examples.

Common patterns

Guarded configuration

function createLogger(level) {
  return function log(message) {
    if (!message) return;
    console.log(`[${level}] ${message}`);
  };
}

The returned logger remembers its configured level.

Validation helpers

function minLength(length) {
  return function (value) {
    return value.length >= length;
  };
}

const isLongEnough = minLength(8);

This pattern is common in form validation.

Array methods with closures

function greaterThan(limit) {
  return function (value) {
    return value > limit;
  };
}

const numbers = [3, 7, 12, 20];
const result = numbers.filter(greaterThan(10));
console.log(result); // [12, 20]

The callback passed into filter closes over limit.

Encapsulation in modules

Developers often use closures to keep internal state private inside a function or factory.

Error handling and retries

A retry function may close over configuration such as retry count, delay, or endpoint settings.

Common Mistakes

1. Thinking closures copy values once

Closures keep access to variables, not just a frozen copy.

let value = 1;

function showValue() {
  console.log(value);
}

value = 2;
showValue(); // 2

The function sees the current value of value.

2. Confusing separate closures with shared state

function makeCounter() {
  let count = 0;
  return function () {
    count++;
    return count;
  };
}

const a = makeCounter();
const b = makeCounter();

a and b do not share the same count.

3. Using var in loops incorrectly

This is a classic closure issue.

Broken code

for (var i = 0; i < 3; i++) {
  setTimeout(function () {
    console.log(i);
  }, 100);
}

Output:

3
3
3

Why? Because all callbacks close over the same i.

Better solution with let

for (let i = 0; i < 3; i++) {
  setTimeout(function () {
    console.log(i);
  }, 100);
}

Output:

0
1
2

let creates a new binding for each iteration.

4. Keeping unnecessary data alive

If a closure references large objects, that data may stay in memory longer than needed. Keep closures focused on only the values they need.

5. Expecting outer variables to be accessible directly

You cannot access a closed-over variable from outside unless the function exposes it somehow.

Comparisons

Closures vs related ideas

ConceptWhat it meansHow it differs from a closure
Regular function callA function runs and uses available variablesA closure specifically remembers outer variables after creation
Global variableA variable accessible from many placesA closure can keep data private instead of global
Object propertyState stored on an objectA closure stores state in lexical scope, not on a public object
Class private fieldPrivate data inside a classClosures provide privacy without requiring classes
ParameterInput passed into a function callA closure can remember values without passing them every time

Closure vs object for state

function createCounterClosure() {
  let count = 0;
  return function () {
    return ++count;
  };
}

const counter = {
  count: 0,
  next() {
    return ++this.count;
  }
};

Differences

  • Closure state is hidden unless exposed
  • Object state is usually accessible as a property
  • Closures are great for small private state
  • Objects are often better when you need many related methods or visible structure
Take Quiz

Cheat Sheet

Closure quick reference

Definition

A closure is a function that remembers variables from its outer lexical scope.

Common pattern

function outer() {
  let value = 10;

  return function inner() {
    return value;
  };
}

Key rules

  • Functions can access variables from outer scopes
  • Returned inner functions can keep using those variables later
  • Each call to the outer function usually creates a new closure
  • Closures remember variables, not just initial values
  • Closures are created automatically in JavaScript

Useful for

  • Private state
  • Counters
  • Event handlers
  • Callbacks
  • Function factories
  • Validation helpers

Loop warning

Prefer let over var in loops when closures are involved.

Mental shortcut

A closure is a function plus its remembered scope.

FAQ

What is a closure in JavaScript?

A closure is a function that keeps access to variables from the scope where it was created, even after that outer scope has finished.

Why are closures useful?

They help preserve state, create private data, configure reusable functions, and make callbacks more powerful.

Are closures created manually?

No. JavaScript creates them automatically whenever a function uses variables from an outer scope.

Do closures copy variable values?

Usually no. They keep access to the variable itself, so they can see updated values.

Are closures only used with returned functions?

No. They also appear in callbacks, event handlers, timers, and methods defined inside other functions.

Can closures cause bugs?

Yes. A common issue is using var in loops, causing multiple functions to share the same variable.

Do closures affect memory?

They can keep referenced data alive longer, so avoid capturing large unnecessary objects.

Are closures the same as private variables?

Not exactly, but closures are one common way to create private state in JavaScript.

Take Quiz

Mini Project

Description

Build a small counter factory that demonstrates private state with closures. This project shows how a function can create and return methods that remember internal variables without exposing them directly. It is a practical example of how closures are used to manage state safely.

Goal

Create a counter generator that can increment, decrement, and read a private count value using closures.

Requirements

Create a function that initializes a private count value. Return an object with increment, decrement, and getValue methods. Ensure the count value cannot be changed directly from outside the returned object. Create two independent counters and show that they do not share state.

Take Quiz

Keep learning

Related questions

View more