JavaScriptConcepts

← Back to Home

JavaScript Closures: In-Depth Guide with Examples

A closure is a function that has access to the parent scope, even after the parent function has closed. A closure is formed when a function remembers the variables fro its lexical scope even when the function is executed outside its lexical scope.


1. Basic Closure Example

function outerFunction() {
    let outerVariable = 'I am from outer!';
    function innerFunction() {
        console.log(outerVariable);
    }
    return innerFunction;
}
const closure1 = outerFunction();
closure1(); // Output: I am from outer!

Explanation:


2. Closure with Private Variables (Data Encapsulation)

function makeCounter() {
    let count = 0;
    return function() {
        count++;
        return count;
    };
}
const counter = makeCounter();
counter(); // 1
counter(); // 2
counter(); // 3

Explanation:


3. Closures in Loops (Common Pitfall & Solution)

Problem:

var funcs = [];
for (var i = 0; i < 3; i++) {
    funcs.push(function() {
        console.log('Problem:', i);
    });
}
funcs[0](); // 3
funcs[1](); // 3
funcs[2](); // 3

Solution 1: IIFE

var funcsFixed = [];
for (var j = 0; j < 3; j++) {
    (function(jCopy) {
        funcsFixed.push(function() {
            console.log('Fixed:', jCopy);
        });
    })(j);
}
funcsFixed[0](); // 0
funcsFixed[1](); // 1
funcsFixed[2](); // 2

Solution 2: Use let

let funcsLet = [];
for (let k = 0; k < 3; k++) {
    funcsLet.push(function() {
        console.log('Let:', k);
    });
}
funcsLet[0](); // 0
funcsLet[1](); // 1
funcsLet[2](); // 2

Explanation:


4. Closures for Function Factories

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

Explanation:


5. Closures in Asynchronous Code

Problem:

for (var m = 0; m < 3; m++) {
    setTimeout(function() {
        console.log('Async Problem:', m);
    }, 100);
}
// All log 3

Solution:

for (let n = 0; n < 3; n++) {
    setTimeout(function() {
        console.log('Async Fixed:', n);
    }, 200);
}
// Logs 0, 1, 2

Explanation:


6. Practical Example: Hiding Implementation Details

function Person(name) {
    let _name = name;
    return {
        getName: function() { return _name; },
        setName: function(newName) { _name = newName; }
    };
}
const p = Person('Alice');
p.getName(); // Alice
p.setName('Bob');
p.getName(); // Bob

Explanation:


7. Interview Question: What is a closure?

A closure is a function that remembers its lexical scope even when the function is executed outside that scope. It allows functions to access variables from an enclosing scope, even after that scope has closed.

8. Interview Question: Why are closures useful?


9. Advanced: Currying with Closures

function add(a) {
    return function(b) {
        return a + b;
    };
}
const add5 = add(5);
add5(10); // 15

Explanation:


10. Memory Leaks and Closures

Be careful: Closures can cause memory leaks if they retain references to large objects unnecessarily. Always clean up references if not needed.

Example:

function createLeak() {
    let largeArray = new Array(1e6).fill('leak'); // Large object
    return function() {
        // This closure keeps largeArray in memory
        console.log('Still holding largeArray of length:', largeArray.length);
    };
}

let leaky = createLeak();
// Even if we don't need largeArray anymore, it's not garbage collected
// because the closure (leaky) still references it.
// To avoid the leak, set leaky = null when done:
// leaky = null;

Summary

Closures are a powerful feature in JavaScript, enabling data privacy, function factories, and more. Understanding closures is essential for interviews and real-world development.


setTimeout, Closures, and Loop Pitfalls

This section explains how closures interact with setTimeout and loops, a common source of confusion in JavaScript.

Example 1: The Problem with var in Loops

function y() {
    for (var i = 1; i <= 5; i++) {
        setTimeout(() => {
            console.log(i);
        }, i * 1000);
    }
}
 y();

What happens?

Example 2: Fix with IIFE (Immediately Invoked Function Expression)

function z() {
    for (var i = 1; i <= 5; i++) {
        (function(closeI) {
            setTimeout(() => {
                console.log(closeI);
            }, closeI * 1000);
        })(i);
    }
}
 z();

How does this work?

Example 3: Fix with let

function a() {
    for (let i = 0; i < 5; i++) {
        setTimeout(() => {
            console.log(i);
        }, i * 1000);
    }
}
 a();

How does this work?

Key Concepts

Summary:

When using asynchronous functions like setTimeout inside loops, always be mindful of variable scope. Use let or an IIFE to avoid common pitfalls with closures and var.


Practice: Try modifying the examples in closures.js & setTimeOutPractice.js and observe the results to deepen your understanding.

scope in JavaScript

Image

Image

Image

Image


1. Scope — Formal Definition

Scope defines the region of the program where an identifier (variable, function, class) can be accessed.

JavaScript uses lexical (static) scoping, meaning:


cope in JavaScript (Foundational Model)

Scope = where a variable is accessible in code. JavaScript scope is lexical (static), determined at parse time, not runtime.

1.1 Types of Scope

Scope Type Introduced By Visibility
Global Scope outside all functions everywhere
Function Scope function entire function body
Block Scope {} with let / const block only
Module Scope ES Modules file-only
let g = 10; // global

function f() {
  let a = 1; // function scope
  if (true) {
    let b = 2; // block scope
  }
}

Key rule:

var → function scoped

let / const → block scoped

2. Types of Scope in JavaScript

2.1 Global Scope

Declared outside all functions / blocks.

let a = 10;

function f() {
  console.log(a);
}

2.2 Function Scope

Created by function.

function test() {
  var x = 5;
  let y = 6;
}

2.3 Block Scope

Created by {} only with let and const.

if (true) {
  let a = 1;
  const b = 2;
}

2.4 Module Scope (ES Modules)

// file.js
const secret = 42;
export {};

3. Lexical Scope (Most Important Rule)

A function can access variables from its own scope and all parent scopes, determined by where it is written.

let x = 100;

function outer() {
  let y = 200;
  function inner() {
    console.log(x, y);
  }
  inner();
}

Lexical scope is one-directional (inside → outside).


4. Scope Chain

When JavaScript resolves a variable:

  1. Look in current scope
  2. If not found, go to outer scope
  3. Continue until global scope
  4. If still not found → ReferenceError
let a = 1;

function f() {
  let b = 2;
  function g() {
    let c = 3;
    console.log(a, b, c);
  }
  g();
}

Scope chain:

g → f → global

5. Execution Context Model (Internal Engine View)

Each function invocation creates an Execution Context containing:

Execution Contexts are pushed onto the Call Stack.


6. Closure — Formal Definition

A closure is a function that retains access to variables from its lexical environment even after the outer function has finished execution.

Closures are:


7. Why Closures Exist (Key Insight)

Closures exist because:


8. Closure Creation — Step-by-Step

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

const counter = outer();

Internal Behavior

  1. outer() invoked
  2. count created in lexical environment
  3. inner returned
  4. Call stack frame of outer removed
  5. count moved to heap
  6. inner holds reference to count
counter(); // 1
counter(); // 2

9. Closure Retains Reference, Not Value

function test() {
  let x = 10;
  return () => console.log(x);
}

const fn = test();
x = 100; // unrelated
fn(); // 10

The closure references its own lexical environment, not global changes.


10. Multiple Closures, Independent State

function createCounter() {
  let c = 0;
  return () => ++c;
}

const c1 = createCounter();
const c2 = createCounter();

c1(); // 1
c1(); // 2
c2(); // 1

Each invocation creates a new lexical environment.


11. Common Closure Use Cases

11.1 Data Encapsulation (Private Variables)

function bankAccount(balance) {
  return {
    deposit(x) { balance += x; },
    getBalance() { return balance; }
  };
}

11.2 Function Factories

function multiplyBy(n) {
  return x => x * n;
}

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

11.3 Callbacks and Async

function fetchData() {
  let token = "auth123";
  setTimeout(() => {
    console.log(token);
  }, 1000);
}

Async does not break closure.


12. Classic Interview Bug — var in Loops

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

Output:

3
3
3

Explanation

Fix

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

13. Closure vs Scope (Critical Distinction)

Aspect Scope Closure
What Accessibility Lifetime
Exists Always Only when referenced
Memory Stack-based Heap-retained
Purpose Name resolution State preservation

14. Memory Implications (Advanced)

Closures can prevent garbage collection.

function heavy() {
  let large = new Array(1e6);
  return () => large.length;
}

As long as closure exists:

Mitigation:

large = null;

15. Closures in DSA / Real Systems

Closures are used in:

Memoization Example

function memoize(fn) {
  const cache = {};
  return function(n) {
    if (cache[n]) return cache[n];
    return cache[n] = fn(n);
  };
}

16. Mental Model (One-Line)

Scope defines visibility. Closure defines persistence.


17. Interview-Grade Summary

encapsulation with closures

function bankAccount(balance) {
  return {
    deposit(x) { balance += x; },
    getBalance() { return balance; }
  };
}