Advanced Closures

Powerful patterns built on closure mechanics.

Currying

Transform a function with multiple arguments into a chain of single-argument functions.

// Normal function:
function add(a, b, c) {
  return a + b + c;
}
add(1, 2, 3); // 6

// Curried version:
function curriedAdd(a) {
  return function(b) {
    return function(c) {
      return a + b + c;
    };
  };
}
curriedAdd(1)(2)(3); // 6

Currying breaks a multi-argument function into nested single-argument functions. Each closure remembers its argument.

1 / 3

Partial Application

Fix some arguments and return a function that takes the rest.

// Create specialized functions from general ones:
function log(level, timestamp, message) {
  console.log(`[${level}] ${timestamp}: ${message}`);
}

// Partial: fix the level
function createLogger(level) {
  return (message) => {
    log(level, new Date().toISOString(), message);
  };
}

const error = createLogger("ERROR");
const info = createLogger("INFO");

error("Connection failed");
// [ERROR] 2024-01-15T...: Connection failed
info("Server started");
// [INFO] 2024-01-15T...: Server started
// Using .bind() for partial application:
function multiply(a, b) {
  return a * b;
}

const double = multiply.bind(null, 2); // fix a=2
const triple = multiply.bind(null, 3); // fix a=3

double(5); // 10
triple(5); // 15

Module Pattern

Use closures to create private state with a public API.

// The Revealing Module Pattern:
const Counter = (() => {
  // Private state (enclosed)
  let count = 0;
  const history = [];

  // Private function
  function log(action) {
    history.push({ action, count, time: Date.now() });
  }

  // Public API (returned)
  return {
    increment() { count++; log("inc"); },
    decrement() { count--; log("dec"); },
    getCount() { return count; },
    getHistory() { return [...history]; },
  };
})();

Counter.increment();
Counter.getCount();  // 1
Counter.count;       // undefined (private!)
Counter.history;     // undefined (private!)

// Module with private state:

0

count is private — only accessible through the API

Memoization

Cache expensive computation results using closures.

function memoize(fn) {
  const cache = new Map(); // enclosed — persists!
  
  return function(...args) {
    const key = JSON.stringify(args);
    
    if (cache.has(key)) {
      console.log("Cache hit!");
      return cache.get(key);
    }
    
    const result = fn(...args);
    cache.set(key, result);
    return result;
  };
}

// Usage:
const expensiveCalc = memoize((n) => {
  console.log("Computing...");
  return n * n * n;
});

expensiveCalc(5); // "Computing..." → 125
expensiveCalc(5); // "Cache hit!" → 125 (instant)
expensiveCalc(3); // "Computing..." → 27
// Memoized Fibonacci (exponential → linear):
const fib = memoize(function(n) {
  if (n <= 1) return n;
  return fib(n - 1) + fib(n - 2);
});

fib(50); // instant! (without memo: would take forever)

Closure & Scope

Advanced scope interactions and gotchas.

// Closures share the same scope object:
function makeActions() {
  const actions = [];
  for (var i = 0; i < 3; i++) {
    actions.push(() => console.log(i));
  }
  return actions;
}
const acts = makeActions();
acts[0](); // 3 (not 0!)
acts[1](); // 3
acts[2](); // 3

All three closures share the same 'i' variable (var is function-scoped). By the time they run, i = 3.

💡 Closures capture variables by reference, not value
1 / 3

FAQ

Common questions about advanced closure patterns.