# JavaScript Closures: How They Work and Why They Matter

Source: https://devdocs10.com/articles/javascript/js-closures  
By Murat Bo · Updated 2026-10-17 · DevDocs10 (https://devdocs10.com)

Closures are one of the most important ideas in JavaScript functions. They let a function remember variables from the scope where it was created, even after that outer scope has finished running.

> **Quick answer:** A closure is created when an inner function keeps access to variables from its outer function. This is why JavaScript functions can “remember” state between calls, build private data, and power many common patterns.

> **Difficulty:** Beginner to Intermediate
>
> **You'll understand this better if you know:** how functions work in JavaScript, what scope means, and the difference between local and global variables.

## 1. What Is Closures?

A closure is a function plus the variables it can still access from the place where it was created. In JavaScript, every function is created with a scope chain, and an inner function can keep access to the outer function’s variables even after the outer function returns.

- Closures are based on `lexical scoping`, which means scope is determined by where code is written.
- They allow functions to retain access to outer variables.
- They are created naturally whenever a function uses variables from an outer scope.
- They are not a special syntax; they are a behavior of functions.

## 2. Why Closures Matter

Closures make JavaScript practical for real applications. They help you write functions that keep private state, create reusable function factories, handle events, and remember data between calls without using global variables.

Without closures, many common JavaScript patterns would be much harder to write cleanly. They are also behind a lot of callback-based code, timers, and module patterns.

## 3. Basic Syntax

### The smallest closure example

This example shows a function returning another function. The inner function can still use `name` after the outer function has finished.

```javascript
function createGreeter(name) {
  return function () {
    return `Hello, ${name}!`;
  };
}

const greetAlice = createGreeter("Alice");
console.log(greetAlice());
```

Output (tested with Node 24.21.0):

```text
Hello, Alice!
```

The inner function forms a closure over `name`. Even though `createGreeter` has already returned, the returned function still remembers the value.

### What makes it a closure?

Two things happen together:

- An inner function is defined inside an outer function.
- The inner function uses a variable from the outer function.

That is enough to create a closure in JavaScript.

## 4. Step-by-Step Examples

### Example 1: Remembering a value

This example returns a counter function that remembers its own private state.

```javascript
function makeCounter() {
  let count = 0;

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

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

Output (tested with Node 24.21.0):

```text
1
2
```

Each call to `counterA` still sees the same `count` variable. That is the closure keeping the state alive.

### Example 2: Independent instances

Each call to the outer function creates a new closure with its own separate data.

```javascript
function createScoreTracker() {
  let score = 0;

  return {
    addPoint() {
      score += 1;
      return score;
    },
    getScore() {
      return score;
    }
  };
}

const playerOne = createScoreTracker();
const playerTwo = createScoreTracker();

playerOne.addPoint();
playerOne.addPoint();
playerTwo.addPoint();

console.log(playerOne.getScore()); // 2
console.log(playerTwo.getScore()); // 1
```

Output (tested with Node 24.21.0):

```text
2
1
```

Each object has methods that share the same private `score`, but the two trackers do not interfere with each other.

### Example 3: Using closures in loops

Closures are often used when a callback needs to remember a value from a loop.

```javascript
function buildHandlers() {
  const handlers = [];

  for (let i = 1; i <= 3; i++) {
    handlers.push(function () {
      return i * 2;
    });
  }

  return handlers;
}

const handlers = buildHandlers();
console.log(handlers[0]()); // 2
console.log(handlers[1]()); // 4
console.log(handlers[2]()); // 6
```

Output (tested with Node 24.21.0):

```text
2
4
6
```

Because `let` creates a new binding for each loop iteration, each function remembers the value it was created with.

### Example 4: A closure used for validation

Closures can also enforce rules by keeping configuration hidden inside a function.

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

const isValidUsername = createLengthValidator(8);

console.log(isValidUsername("alex1234")); // true
console.log(isValidUsername("sam")); // false
```

Output (tested with Node 24.21.0):

```text
true
false
```

The validator remembers `minLength` without exposing it to the rest of the program.

## 5. Practical Use Cases

- Creating private data that should not be changed directly from outside a function.
- Building factory functions that generate customized behavior.
- Writing event handlers that need to remember setup data.
- Using timers or asynchronous callbacks that refer to earlier values.
- Adding helper functions inside modules without leaking variables globally.

## 6. Common Mistakes

### Mistake 1: Expecting closures to copy values instead of keeping references

A closure does not freeze a variable’s value at the moment the function is created. It keeps access to the variable itself, so later changes are visible.

**Problem:** This code expects the returned function to always print the original value, but the outer variable changes before the function runs.

```javascript
function makeLogger() {
  let message = "Start";

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

const log = makeLogger();
message = "Changed";
```

**Fix:** Keep the change inside the outer function or pass the value as an argument instead of trying to assign to an out-of-scope variable.

```javascript
function makeLogger() {
  let message = "Start";

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

const log = makeLogger();
log();
```

Output (tested with Node 24.21.0):

```text
Start
```

The corrected version works because the closure reads the variable that belongs to its own lexical environment.

### Mistake 2: Reusing a loop variable in asynchronous callbacks

This is a classic closure bug when code uses `var` in a loop and schedules callbacks that run later.

**Problem:** All callbacks see the same final value, so the output is not what beginners expect.

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

Output (tested with Node 24.21.0):

```text
4
4
4
```

**Fix:** Use `let` so each iteration gets its own binding.

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

Output (tested with Node 24.21.0):

```text
1
2
3
```

The fixed version works because each callback closes over a different `i`.

### Mistake 3: Confusing closure scope with global variables

If a variable is not declared in the right place, JavaScript looks for it in the outer scopes. That can lead to accidental global behavior or a `ReferenceError`.

**Problem:** The function tries to use `count` without declaring it in a scope that all related functions can share.

```javascript
function createBrokenCounter() {
  return function () {
    count += 1;
    return count;
  };
}

const brokenCounter = createBrokenCounter();
console.log(brokenCounter());
```

Output (tested with Node 24.21.0):

```text
Uncaught ReferenceError: count is not defined
```

**Fix:** Declare the variable in the outer function so the inner function closes over it.

```javascript
function createCounter() {
  let count = 0;

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

This works because both functions share the same lexical environment, and the variable exists for the closure to use.

## 7. Best Practices

### Practice 1: Use closures for private state, not for everything

Closures are great for hiding data, but they are not always the simplest solution. If data needs to be shared broadly, a plain object or class may be easier to read.

```javascript
function createBankAccount() {
  let balance = 0;

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

Use this style when the state should stay private and only a small API should be exposed.

### Practice 2: Keep closed-over data small

Closures can keep objects alive longer than expected. If you close over a large structure that is no longer needed, memory may stay in use until the closure is released.

```javascript
function makeHandler(userId) {
  return function () {
    return `User: ${userId}`;
  };
}
```

This is better than capturing an entire object when you only need one small property.

### Practice 3: Name factory functions clearly

Functions that return closures are easier to maintain when their names describe what they create.

```javascript
function createPrefixer(prefix) {
  return function (text) {
    return `${prefix}: ${text}`;
  };
}
```

Clear names make it obvious that the outer function configures and returns a new function.

## 8. Limitations and Edge Cases

- Closures keep variables alive, so they can extend object lifetimes and increase memory use if they capture too much data.
- They capture variables by reference to the binding, not by making a snapshot of the value.
- Each call to an outer function creates a new closure environment, which is useful but can add overhead in large loops if overused carelessly.
- Browser event handlers and timers often reveal closure behavior later than expected, which can look like a bug if you expect immediate execution.
- Some closure-related bugs appear only with asynchronous code, so the output can look correct in the source but wrong at runtime.

## 9. Practical Mini Project

Let’s build a small task tracker using closures to keep the task list private. The returned methods can add tasks, list tasks, and remove tasks without exposing the internal array directly.

```javascript
function createTaskList() {
  let tasks = [];

  return {
    add(task) {
      tasks.push(task);
    },
    remove(task) {
      tasks = tasks.filter(function (item) {
        return item !== task;
      });
    },
    all() {
      return [...tasks];
    }
  };
}

const todo = createTaskList();

todo.add("Write docs");
todo.add("Review code");
todo.remove("Write docs");

console.log(todo.all()); // ["Review code"]
```

Output (tested with Node 24.21.0):

```text
[ "Review code" ]
```

This mini project uses a closure to keep `tasks` private while still offering useful operations through methods.

## 10. Key Points

- A closure is a function that remembers variables from its outer scope.
- Closures are created by lexical scoping, not by any special keyword.
- They are useful for private state, callbacks, and factory functions.
- Common bugs come from loop variables, asynchronous code, and misunderstanding references.
- Use closures when they make the code clearer and more focused.

## 11. Practice Exercise

Build a function that returns a counter with three methods:

- `increment()` increases the count by 1.
- `decrement()` decreases the count by 1.
- `value()` returns the current count.

Expected output:

- Starting value is `0`.
- After two increments and one decrement, the value should be `1`.

Hint: store the count in a variable inside the outer function, and return an object whose methods share that variable.

Solution:

```javascript
function createCounter() {
  let count = 0;

  return {
    increment() {
      count += 1;
    },
    decrement() {
      count -= 1;
    },
    value() {
      return count;
    }
  };
}

const counter = createCounter();
counter.increment();
counter.increment();
counter.decrement();

console.log(counter.value()); // 1
```

Output (tested with Node 24.21.0):

```text
1
```

## 12. Final Summary

Closures are a core part of JavaScript’s function model. They let inner functions keep access to outer variables, which makes it possible to preserve state, hide data, and build reusable function factories. Once you understand closures, many common JavaScript patterns become easier to read and write.

They are especially useful when you need private state without classes, or when a callback needs to remember values from an earlier step. At the same time, closures can create confusing bugs if you accidentally reuse loop variables, rely on the wrong scope, or keep more data alive than necessary.

If you want to go further, study lexical scoping, function expressions, and how closures behave in asynchronous code. Those topics will make closure-based patterns much easier to reason about in real projects.
