Skip to content
intermediatePhase 32 · JavaScript Fundamentals

Closures

Master closures for data privacy, function factories, and memory management.

45m
0 problems
Topic Progress0%

What are Closures

What are Closures

A closure is a function that remembers the variables from its outer scope, even after the outer function has finished executing.

Basic Example

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

const counter = outer();
counter(); // 1
counter(); // 2
counter(); // 3
// count is still accessible!

How It Works

function createMultiplier(multiplier) {
  // multiplier is 'closed over'
  return function(number) {
    return number * multiplier;
  };
}

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

double(5);  // 10
triple(5);  // 15
// multiplier is remembered!

Scope Chain

function outer() {
  let a = 1;
  
  function middle() {
    let b = 2;
    
    function inner() {
      let c = 3;
      console.log(a, b, c); // 1, 2, 3
    }
    
    return inner;
  }
  
  return middle;
}

const fn = outer()();
fn(); // 1, 2, 3

Lexical Environment

function greet(greeting) {
  return function(name) {
    return `${greeting}, ${name}!`;
  };
}

const sayHello = greet('Hello');
const sayHi = greet('Hi');

sayHello('John'); // "Hello, John!"
sayHi('Jane');   // "Hi, Jane!"

Closures vs Global Variables

// Bad: global variable
let count = 0;
function increment() {
  count++;
}

// Good: closure
function createCounter() {
  let count = 0;
  return {
    increment: () => ++count,
    getCount: () => count
  };
}

Closure Use Cases

Closure Use Cases

Data Privacy / Encapsulation

function createBankAccount(initialBalance) {
  let balance = initialBalance;
  
  return {
    deposit(amount) {
      balance += amount;
      return balance;
    },
    withdraw(amount) {
      if (amount > balance) throw new Error('Insufficient funds');
      balance -= amount;
      return balance;
    },
    getBalance() {
      return balance;
    }
  };
}

const account = createBankAccount(1000);
account.deposit(500);    // 1500
account.withdraw(200);   // 1300
account.getBalance();    // 1300
// balance is private!

Function Factories

function createAdder(base) {
  return function(value) {
    return base + value;
  };
}

const add10 = createAdder(10);
const add100 = createAdder(100);

add10(5);   // 15
add100(5);  // 105

Memoization

function memoize(fn) {
  const cache = {};
  
  return function(...args) {
    const key = JSON.stringify(args);
    if (!(key in cache)) {
      cache[key] = fn(...args);
    }
    return cache[key];
  };
}

const expensiveCalculation = memoize((n) => {
  console.log('Computing...');
  return n * n;
});

expensiveCalculation(5); // Computing... 25
expensiveCalculation(5); // 25 (cached)

Event Handlers

function setupButton(buttonId, message) {
  const button = document.getElementById(buttonId);
  button.addEventListener('click', function() {
    alert(message); // message is closed over
  });
}

setupButton('btn1', 'Hello!');
setupButton('btn2', 'Goodbye!');

Iterators

function createRangeIterator(start, end) {
  let current = start;
  
  return {
    next() {
      if (current <= end) {
        return { value: current++, done: false };
      }
      return { done: true };
    }
  };
}

const iter = createRangeIterator(1, 5);
iter.next(); // { value: 1, done: false }
iter.next(); // { value: 2, done: false }

Debounce/Throttle

function debounce(fn, delay) {
  let timeoutId;
  
  return function(...args) {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => fn(...args), delay);
  };
}

const debouncedSearch = debounce((query) => {
  console.log('Searching:', query);
}, 300);

Memory Considerations

Memory Considerations

Closures and Memory

function createHeavyClosure() {
  const largeArray = new Array(1000000).fill('data');
  
  return function() {
    // largeArray is kept in memory
    return largeArray.length;
  };
}

const fn = createHeavyClosure();
// largeArray stays in memory as long as fn exists

Memory Leaks

// Bad: unintended closure
function setup() {
  const element = document.getElementById('app');
  
  element.addEventListener('click', function() {
    // 'element' is closed over, can't be garbage collected
    console.log('clicked');
  });
}

// Better: remove event listener
function setup() {
  const element = document.getElementById('app');
  
  function handleClick() {
    console.log('clicked');
  }
  
  element.addEventListener('click', handleClick);
  
  return function cleanup() {
    element.removeEventListener('click', handleClick);
  };
}

Breaking Closures

// Nullify references
function createClosure() {
  let data = new Array(1000000);
  
  return function() {
    return data.length;
  };
}

let fn = createClosure();
fn(); // Works

// Break the closure
fn = null; // data can now be garbage collected

WeakMap for Private Data

const privateData = new WeakMap();

class User {
  constructor(name) {
    privateData.set(this, { name });
  }
  
  getName() {
    return privateData.get(this).name;
  }
}

// When User instance is garbage collected,
// its private data is also collected

IIFE for Cleanup

function processData() {
  const cache = new Map();
  
  // Process and clean up
  const result = (function() {
    // Use cache
    return 'result';
  })();
  
  // cache is still accessible
  return result;
}

Best Practices

// 1. Don't close over unnecessary data
function bad() {
  const hugeData = getHugeData();
  return function() {
    return hugeData.length; // Closes over hugeData
  };
}

function good() {
  const hugeData = getHugeData();
  const length = hugeData.length;
  return function() {
    return length; // Only closes over length
  };
}

// 2. Use WeakMap/WeakSet for object references
// 3. Clean up event listeners
// 4. Set references to null when done

Practice Problems

0/3solved
Build Closures Component

Create a reusable React component implementing Closures. Include proper state management and accessibility.

Solution
// Production-ready component with:
// - Proper TypeScript types
// - Accessibility (ARIA)
// - Error boundaries
// - Loading states
// - Memoization where needed
Closures Testing

Write unit and integration tests for Closures using React Testing Library.

Solution
// Test coverage:
// 1. Rendering tests
// 2. Interaction tests
// 3. Edge case tests
// 4. Accessibility tests
Closures Performance

Optimize Closures for performance. Consider memoization, code splitting, and bundle size.

Solution
// Optimization techniques:
// 1. React.memo / useMemo / useCallback
// 2. Code splitting with lazy()
// 3. Virtual scrolling for lists
// 4. Image lazy loading
// 5. Bundle analysis

Quiz

1. What is a closure?

Question 1 options

2. When does a closure keep variables in memory?

Question 2 options

3. What is a common use case for closures?

Question 3 options

4. How do you break a closure?

Question 4 options

5. What problem can closures cause?

Question 5 options

Flashcards

Question

What is a closure?

Answer

A function that remembers and can access variables from its outer scope, even after the outer function has returned.

Question

When do closed-over variables get garbage collected?

Answer

When the closure function no longer exists and no references to it remain.

Question

What is a common use case for closures?

Answer

Data privacy, memoization, function factories, and event handlers.

Question

How can closures cause memory leaks?

Answer

By keeping references to large objects that are no longer needed.

Question

What is a function factory?

Answer

A function that returns new functions with preset parameters, using closures to remember those parameters.

Revision Notes

Key Takeaways

  • 1.Closures remember variables from their outer scope
  • 2.They enable data privacy and encapsulation
  • 3.Variables stay in memory while the closure exists
  • 4.Common uses: counters, factories, memoization
  • 5.Be mindful of memory with closures

Interview Tips

  • Explain closures with a code example
  • Know the difference between closures and global variables
  • Understand memory implications of closures
  • Be able to create private variables with closures

Cheat Sheet

Closures Cheat Sheet

What is a Closure?

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

Use Cases

  1. Data Privacy
function createCounter() {
  let count = 0;
  return {
    increment: () => ++count,
    getCount: () => count
  };
}
  1. Function Factories
function createMultiplier(n) {
  return (x) => x * n;
}
  1. Memoization
function memoize(fn) {
  const cache = {};
  return (...args) => {
    const key = JSON.stringify(args);
    return cache[key] || (cache[key] = fn(...args));
  };
}

Memory

  • Closures keep variables in memory
  • Set references to null to break closures
  • Use WeakMap for object references