JavaScript  

What are closures in JavaScript

Introduction

A closure is when a function keeps access to variables from its outer scope, even after that outer function has finished running.

What is a Closure?

When a function is created inside another function, it automatically remembers the variables around it. Even when the outer function is done running, the inner function still has access to those variables.

JavaScript does this by design. The inner function carries a reference to the variables it used when it was created. This is called a closure.

Basic Example

function outer() {
    let count = 0;

    function inner() {
        count++;
        console.log(count);
    }
    return inner;
}
const fn = outer();
fn(); // 1
fn(); // 2
fn(); // 3

What’s happening here?

  • outer() creates a variable count and defines a function inner()
  • It returns the inner() function
  • It holds that inner() function
  • Every time you call fn(), it increases count and logs it

Even though outer() has already finished, inner() still remembers the variable count. That’s the closure.

Why are Closures Useful?

Closures help you.

  • Keep state (like a counter or value)
  • Hide internal variables so no one else can touch them
  • Build reusable, customized functions

Let’s look at some practical examples.

Use Case 1. Private Variables

function createCounter() {
    let count = 0;
    return {
        increment: function () {
            count++;
            return count;
        },
        decrement: function () {
            count--;
            return count;
        }
    };
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.decrement()); // 1

Here

  • Count is not available outside
  • Only increment and decrement can access it
  • This keeps the state private

Use Case 2. Custom Functions with Pre-set Values

function multiply(x) {
    return function (y) {
        return x * y;
    };
}
const double = multiply(2);
console.log(double(5));  // 10
console.log(double(10)); // 20

The double function remembers that x is 2. That’s a closure in action.

Use Case 3. Event Handlers That Track State

function setupClickTracker(id) {
    let clicks = 0;

    document.getElementById(id).addEventListener('click', function () {
        clicks++;
        console.log("Clicked " + clicks + " times");
    });
}

Each button you set up like this will keep its click count. That’s possible because of closures.

Common Mistake: Loop with var

for (var i = 0; i < 3; i++) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
}
  • Expected: 0 1 2
  • Actual: 3 3 3

Why? Because var is function-scoped. All the functions share the same i. By the time the timeout runs, i has already become 3.

Fix with let

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

This prints 0 1 2 as expected. Because let is block-scoped, a new i is created for each loop iteration. Each closure gets the correct value.

How Closures Work Internally?

When you return a function from another function, JavaScript doesn't throw away the variables the inner function used. It keeps them alive as long as that inner function exists.

So even if the outer function is done running, the variables are still available to the inner function. That’s what allows the inner function to keep working with the original values.

Memory Concerns

Closures can cause memory problems if you're not careful. For example.

function setup() {
    const largeData = new Array(1000000).fill('*');

    return function () {
        console.log("Running...");
    };
}
const fn = setup(); // largeData is now stuck in memory

Even though largeData is never used again, it's not removed because the returned function might use it. This can waste memory.

Always think about what variables your closures are holding on to.

Classic Interview Trap

var funcs = [];

for (var i = 0; i < 3; i++) {
    funcs.push(function() {
        console.log(i);
    });
}

funcs[0](); // 3
funcs[1](); // 3
funcs[2](); // 3

All print 3. Why? Same issue as before var is shared.

Fix using IIFE

for (var i = 0; i < 3; i++) {
    (function(index) {
        funcs.push(function() {
            console.log(index);
        });
    })(i);
}

This time, each function remembers its value of index.

Quick Summary

  • A closure is when a function remembers variables from its outer function, even after the outer function is done.
  • Closures help you keep state, create private variables, and write more flexible code.
  • Be careful when using closures in loops or with large data, or you might end up with bugs or memory issues.
  • Always know what variables your function is capturing.