javascript 244 lines · 1 tab

JavaScript closures and lexical scope fundamentals

Alex Chang Feb 2026
1 tab
// Basic closure example
function outer() {
  const message = 'Hello from outer';

  function inner() {
    console.log(message); // Accesses outer variable
  }

  return inner;
}

const closure = outer();
closure(); // "Hello from outer"

// Counter with closure (data privacy)
function createCounter() {
  let count = 0; // Private variable

  return {
    increment() {
      count++;
      return count;
    },
    decrement() {
      count--;
      return count;
    },
    getCount() {
      return count;
    }
  };
}

const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getCount());  // 2
// console.log(counter.count); // undefined - private!

// Multiple independent closures
const counter1 = createCounter();
const counter2 = createCounter();

counter1.increment(); // 1
counter2.increment(); // 1
counter2.increment(); // 2
console.log(counter1.getCount()); // 1 (independent)

// Closure in loops (classic problem)
// Wrong way
for (var i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i); // Prints 3, 3, 3
  }, 100);
}

// Fixed with IIFE
for (var i = 0; i < 3; i++) {
  (function(j) {
    setTimeout(function() {
      console.log(j); // Prints 0, 1, 2
    }, 100);
  })(i);
}

// Modern fix with let
for (let i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i); // Prints 0, 1, 2
  }, 100);
}

// Function factory with closures
function multiplier(factor) {
  return function(number) {
    return number * factor;
  };
}

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

console.log(double(5));  // 10
console.log(triple(5));  // 15

// Private methods pattern
function BankAccount(initialBalance) {
  let balance = initialBalance; // Private

  // Private method
  function validateAmount(amount) {
    return typeof amount === 'number' && amount > 0;
  }

  // Public methods
  this.deposit = function(amount) {
    if (validateAmount(amount)) {
      balance += amount;
      return balance;
    }
    throw new Error('Invalid amount');
  };

  this.withdraw = function(amount) {
    if (validateAmount(amount) && amount <= balance) {
      balance -= amount;
      return balance;
    }
    throw new Error('Invalid or insufficient amount');
  };

  this.getBalance = function() {
    return balance;
  };
}

const account = new BankAccount(100);
account.deposit(50);   // 150
account.withdraw(30);  // 120
console.log(account.getBalance()); // 120
// account.balance // undefined - private!

// Closure with event handlers
function setupButton(buttonId, message) {
  const button = document.getElementById(buttonId);

  button.addEventListener('click', function() {
    console.log(message); // Closure over message
  });
}

setupButton('btn1', 'Button 1 clicked');
setupButton('btn2', 'Button 2 clicked');

// Module pattern
const calculator = (function() {
  // Private variables and functions
  let result = 0;

  function validateNumber(n) {
    if (typeof n !== 'number') {
      throw new Error('Not a number');
    }
  }

  // Public API
  return {
    add(n) {
      validateNumber(n);
      result += n;
      return this;
    },
    subtract(n) {
      validateNumber(n);
      result -= n;
      return this;
    },
    multiply(n) {
      validateNumber(n);
      result *= n;
      return this;
    },
    getResult() {
      return result;
    },
    reset() {
      result = 0;
      return this;
    }
  };
})();

calculator.add(5).multiply(3).subtract(2);
console.log(calculator.getResult()); // 13

// Memoization with closure
function memoize(fn) {
  const cache = {}; // Closure variable

  return function(...args) {
    const key = JSON.stringify(args);

    if (key in cache) {
      console.log('From cache');
      return cache[key];
    }

    console.log('Calculating...');
    const result = fn(...args);
    cache[key] = result;
    return result;
  };
}

const expensiveSum = memoize((a, b) => {
  // Simulate expensive operation
  for (let i = 0; i < 1000000000; i++) {}
  return a + b;
});

console.log(expensiveSum(5, 3)); // Calculating... 8
console.log(expensiveSum(5, 3)); // From cache 8

// Partial application
function partial(fn, ...fixedArgs) {
  return function(...remainingArgs) {
    return fn(...fixedArgs, ...remainingArgs);
  };
}

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

const sayHello = partial(greet, 'Hello');
console.log(sayHello('Alice')); // "Hello, Alice!"
console.log(sayHello('Bob'));   // "Hello, Bob!"

// Lexical scope example
const globalVar = 'global';

function outer() {
  const outerVar = 'outer';

  function middle() {
    const middleVar = 'middle';

    function inner() {
      const innerVar = 'inner';

      // Can access all outer scopes
      console.log(innerVar);  // 'inner'
      console.log(middleVar); // 'middle'
      console.log(outerVar);  // 'outer'
      console.log(globalVar); // 'global'
    }

    inner();
  }

  middle();
}

outer();
1 file · javascript Explain with highlit

Closures allow functions to access variables from outer scopes even after outer function returns. I create closures when inner functions reference outer function variables. Lexical scope means functions look up variables where they're defined, not where they're called. Closures enable data privacy by encapsulating variables. The module pattern uses closures to create private state and public interfaces. Using closures for event handlers preserves access to surrounding context. Each closure maintains its own scope chain and variable bindings. Closures power callbacks, higher-order functions, and functional programming patterns. Understanding closure lifetime prevents memory leaks. The closure scope chain includes local scope, outer function scopes, and global scope.


Related snips

sql
-- Simple function
CREATE OR REPLACE FUNCTION get_full_name(
  first_name VARCHAR,
  last_name VARCHAR
)
RETURNS VARCHAR AS $$

Stored procedures and functions in PostgreSQL

postgresql stored-procedures functions
by Maria Garcia 2 tabs
javascript
// Creating a Promise
const myPromise = new Promise((resolve, reject) => {
  const success = true;

  setTimeout(() => {
    if (success) {

Promises and async/await patterns for asynchronous JavaScript

javascript promises async-await
by Alex Chang 1 tab
javascript
import { Controller } from "@hotwired/stimulus"
import Mousetrap from "mousetrap"

export default class extends Controller {
  connect() {
    // Global shortcuts

Keyboard shortcuts with Stimulus and Mousetrap

stimulus javascript ux
by Jordan Lee 2 tabs
javascript
// Get canvas and context
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

// 1. Basic shapes
// Rectangle (filled)

Canvas API for graphics and animations

canvas javascript graphics
by Alex Chang 1 tab
javascript
// Basic event listener
const button = document.getElementById('myButton');

button.addEventListener('click', function(event) {
  console.log('Button clicked!');
  console.log('Event type:', event.type);

Event handling and event delegation patterns in JavaScript

javascript events event-delegation
by Alex Chang 1 tab
javascript
// Basic GET request
fetch('https://api.example.com/users')
  .then(response => {
    console.log('Status:', response.status);
    console.log('OK:', response.ok);
    return response.json();

Fetch API for HTTP requests and AJAX communication

javascript fetch api
by Alex Chang 1 tab

Share this code

Here's the card — post it anywhere.

JavaScript closures and lexical scope fundamentals — share card
Link copied