Explain Codes LogoExplain Codes Logo

How do JavaScript closures work?

javascript
closures
functional-programming
event-handlers
Alex KataevbyAlex Kataev·Feb 13, 2025
TLDR

A closure in JavaScript is a function that can remember and access its enclosing scope (the scope in which it was created) even after that scope has finished executing. This enables functions to have "private variables" which hold on to their value over time. The below example shows a simple closure:

function createCounter() { let count = 0; // A local "secret" variable return () => ++count; // Anonymous function with access to the secret } const myCounter = createCounter(); console.log(myCounter()); // It says: 1 console.log(myCounter()); // Now it says: 2 (It remembered!)

The createCounter function encapsulates the count variable. Calling myCounter increases the count, demonstrating a closure's ability to maintain state.

Understanding closures in-depth

Closures are the bread 'n' butter in JavaScript; they encapsulate variables and function definitions, shielding them from the global scope. They affect memory usage as the hidden ("enclosed") variables persist only as long as they are referenced.

Privacy settings: On

A closure can hide variables and methods, making them inaccessible from the outside, serving as privacy settings for the function. This contains the internal state:

function SecretVault(password) { let _password = password; // More secret than your diary this.isAccessGranted = function(enteredPassword) { return enteredPassword === _password; }; }

The _password is shielded from external access, remaining secret unless you know the secret handshake...umm...password.

Closures within loops

A common pitfall is using closures in loops with var keyword, which results in unexpected behavior, as follows:

for (let i = 0; i < 5; i++) { setTimeout(() => console.log(i), i * 1000); // Spooky time travel with closures! }

The use of let creates a new scope in each iteration, allowing each callback to close over a new i – no sharing!

It's curry o'clock

Closures enable functional programming, such as currying (not the food), where a function with multiple parameters can be transformed into a sequence of functions with a single parameter:

function multiply(a) { return b => a * b; // Savory curry to multiply 'a' and 'b' } const double = multiply(2); // We just need some spicy doubling curry double(5); // 10

It helps create more reusable and modular functions that can sequence operations.

Event handlers: no strings attached

Event handlers often need to keep track of context: they need closures:

let buttonClicked = false; document.querySelector('button').addEventListener('click', () => { buttonClicked = true; // Button has one job and it's finally done it });

Thanks to closure, the event listener can 'remember' the buttonClicked state across user interactions.

IIFE: Declaration and Execution, all in one

Immediately Invoked Function Expressions (IIFE) allow variables to be scoped, not exposed to the globals, a common use for closures:

(function() { let secretMessage = "Hello, World!"; console.log(secretMessage); // Yells out the secret })(); // secretMessage? Never heard of it.

It secures the secretMessage.

Closures and mutable state

A closure's internals aren't stuck in time; you can change them alright:

function createBuffer() { let buffer = ''; // Our mighty storage return { append: str => buffer += str, // Add to the buffer clear: () => buffer = '', // Woosh! It's clean. print: () => console.log(buffer) // Show us the goods }; }

Though declared once, the buffer can be manipulated without being directly accessed from outside.