
Una función en JavaScript recuerda el ámbito en el que se creó, incluso después de que ese ámbito haya terminado de ejecutarse. Descubre qué son los cierres, por qué se produce el error del bucle y cómo utilizarlos en la práctica.
Martin Ferret
26 de mayo de 2026
A closure is a function that has access to the variables of its outer scope, even after that outer function has returned. This is not a trick or a special syntax. It is how JavaScript scope works by design.
Consider a function that returns another function. The inner function references a variable from the outer one:
function makeGreeter(name) {
return function() {
console.log('Hello, ' + name);
};
}
const greetJohn = makeGreeter('John');
greetJohn(); // → Hello, John
When makeGreeter is called, it returns a function and its execution ends. Yet greetJohn still has access to name. That is a closure: the returned function carries its surrounding scope with it.
JavaScript uses lexical scoping: a function's scope is determined by where it is written in the source code, not where it is called from. When a function is defined inside another function, it always has access to the outer function's variables, regardless of when or where it is eventually called.
function outer() {
const message = 'I am from outer';
function inner() {
console.log(message); // accesses outer's variable
}
return inner;
}
const fn = outer();
fn(); // → I am from outer
The variable message is not copied into inner. The inner function holds a reference to the outer scope. As long as inner exists, that scope stays alive.
This is where closures often surprise developers. The closure does not take a snapshot of the variable at the time the function is created. It retains a live reference to it. If the variable changes, the closure sees the new value:
function makeCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = makeCounter();
console.log(counter()); // → 1
console.log(counter()); // → 2
console.log(counter()); // → 3
Each call to counter() increments the same count variable. The closure keeps it alive and private. Nothing outside makeCounter can read or modify count directly.
The most common closure pitfall involves loops and var. Because var is function-scoped, all iterations of the loop share the same variable:
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
// → 3, 3, 3
By the time the callbacks fire, the loop has finished and i is 3. All three closures reference the same i.
The fix is to use let, which creates a new binding for each iteration:
for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
// → 0, 1, 2
Closures are not just a language curiosity. They are the foundation of several everyday patterns in JavaScript.
Private state. A closure can hold data that is inaccessible from the outside, simulating private variables:
function createWallet(initialBalance) {
let balance = initialBalance;
return {
deposit(amount) { balance += amount; },
withdraw(amount) { balance -= amount; },
getBalance() { return balance; }
};
}
const wallet = createWallet(100);
wallet.deposit(50);
console.log(wallet.getBalance()); // → 150
// balance is not accessible from outside
Function factories. Closures let you generate specialized functions from a generic one:
function multiplier(factor) {
return (n) => n * factor;
}
const double = multiplier(2);
const triple = multiplier(3);
console.log(double(5)); // → 10
console.log(triple(5)); // → 15
A closure is not a design pattern or an advanced trick. It is a direct consequence of how JavaScript handles scope: functions carry their environment with them.
Understanding closures means understanding why counters hold state, why loop callbacks behave unexpectedly with var, and why function factories work at all. Once the model is clear, a large part of JavaScript starts to make sense.
Get the latest news and updates on developer certifications. Content is updated regularly, so please make sure to bookmark this page or sign up to get the latest content directly in your inbox.

Cómo funciona realmente el bucle del trabajador de la cola
Domina las colas de Laravel comprendiendo qué ocurre entre bastidores cuando se envían y procesan las tareas. Esta guía analiza los trabajadores de colas, la serialización de modelos, los reintentos, las tareas fallidas, el encadenamiento y el procesamiento por lotes: conceptos clave para crear aplicaciones fiables y superar con éxito los exámenes de certificación de Laravel.
Steve McDougall
25 de junio de 2026

Primeros pasos con rstore en Vue
Una guía paso a paso sobre rstore, el almacén de datos reactivo para Vue con almacenamiento en caché normalizado, consultas tipadas y un sistema de complementos.
Reza Baar
24 de junio de 2026

Promise.withResolvers(): el patrón «Deferred» integrado
Promise.withResolvers() sustituye al patrón «deferred» manual en JavaScript. Una sola desestructuración, sin ejecutor, sin «let». ES2024, compatible con todos los entornos de ejecución modernos.
Martin Ferret
23 de junio de 2026