Anonymous functions and closures are related, but they are not the same feature. An anonymous function has no declared name. A closure is a function that retains access to its lexical scope.
This distinction matters when choosing syntax, creating callbacks, preserving state, or reasoning about this. The examples below use modern JavaScript (ES2015 or later) and run as pasted in a browser console or Node.js.
Function declarations and anonymous function expressions
A function declaration introduces a named function in its surrounding scope. An anonymous function expression creates a function value without giving it an explicit name.
"use strict";
function declaredFunction(name) {
return `Hello, ${name}`;
}
const anonymousFunction = function (name) {
return `Hello, ${name}`;
};
console.log(declaredFunction("Ada"));
console.log(anonymousFunction("Grace"));
The assignment is essential in the second example. A bare function () {} is not a valid function declaration because declarations require names. An anonymous function must appear where JavaScript expects an expression.
Function declarations are initialized when their scope is created, so code can call them before their source line. A function expression is available only after execution reaches its assignment.
"use strict";
console.log(double(4));
function double(value) {
return value * 2;
}
const triple = function (value) {
return value * 3;
};
console.log(triple(4));
Although an anonymous function has no explicit name in source code, modern engines often infer a diagnostic name from the variable or property receiving it. That improves stack traces but does not turn the expression into a declaration.
An anonymous function is useful as a callback when the behavior is short and used once.
"use strict";
const numbers = [1, 2, 3, 4];
const squares = numbers.map(function (number) {
return number * number;
});
console.log(squares);
For recursion or clearer stack traces, use a named function expression. Its name is visible inside the function but does not leak into the surrounding scope.
"use strict";
const factorial = function calculateFactorial(number) {
return number <= 1 ? 1 : number * calculateFactorial(number - 1);
};
console.log(factorial(5));
Arrow functions
Arrow functions provide a shorter expression syntax introduced in ES2015. They are anonymous unless the engine infers a name from their context.
"use strict";
const numbers = [1, 2, 3, 4];
const squares = numbers.map((number) => number * number);
console.log(squares);
Parentheses around one parameter are optional, but many style guides retain them for consistency. An implicit return works only when the function body is a single expression.
To return an object literal implicitly, wrap the object in parentheses. Otherwise, braces are parsed as the function body.
"use strict";
const createUser = (name, role) => ({ name, role });
console.log(createUser("Ada", "admin"));
Arrow functions differ from regular functions in important ways. They do not create their own this, arguments, or prototype, and they cannot be called with new.
Use an arrow for a compact callback or when lexical this is desired. Use a regular function when the caller must supply this, when arguments is needed, or when constructing an object.
Immediately invoked function expressions
An immediately invoked function expression (IIFE) runs as soon as it is created. Parentheses force the parser to treat the anonymous function as an expression, and the final () calls it.
"use strict";
const result = (function (first, second) {
const sum = first + second;
return sum * 2;
})(3, 4);
console.log(result);
Before ES2015 modules and block-scoped declarations, IIFEs were commonly used to avoid adding variables to the global scope. They remain useful for one-time initialization that needs a private temporary scope.
For ordinary block isolation, prefer let and const. For reusable file-level encapsulation, prefer JavaScript modules.
Closures and lexical scope
JavaScript uses lexical scope: a function can access bindings declared where that function was written. A closure is the combination of a function and those surrounding bindings.
The inner function below closes over greeting. It can still read that binding after createGreeter has returned.
"use strict";
function createGreeter(greeting) {
return function (name) {
return `${greeting}, ${name}`;
};
}
const greetPolitely = createGreeter("Good morning");
console.log(greetPolitely("Lin"));
console.log(greetPolitely("Sam"));
Closures capture bindings, not frozen copies of values. If a closed-over binding changes, later calls observe the new value.
"use strict";
function createCounter(initialValue = 0) {
let count = initialValue;
return function () {
count += 1;
return count;
};
}
const nextCount = createCounter(100);
console.log(nextCount());
console.log(nextCount());
console.log(nextCount());
Each call to createCounter creates a separate lexical environment. Counters produced by different calls therefore keep independent state.
"use strict";
function createCounter() {
let count = 0;
return () => ++count;
}
const firstCounter = createCounter();
const secondCounter = createCounter();
console.log(firstCounter());
console.log(firstCounter());
console.log(secondCounter());
Closures in loops: let and var
A classic closure mistake occurs when several functions close over one var binding. var is function-scoped, so every callback below reads the same index after the loop has finished.
"use strict";
const readers = [];
for (var index = 0; index < 3; index += 1) {
readers.push(function () {
return index;
});
}
console.log(readers.map((read) => read()));
The output is [3, 3, 3]. The callbacks do not capture the value at each iteration; they share the single index binding.
Use let for a loop variable. A for loop creates a new binding for each iteration, so every callback closes over the expected value.
"use strict";
const readers = [];
for (let index = 0; index < 3; index += 1) {
readers.push(function () {
return index;
});
}
console.log(readers.map((read) => read()));
The output is [0, 1, 2]. This ES2015 solution is clearer than the older IIFE technique used to copy each var value into a new function scope.
Private state with closures
A factory can expose methods while keeping its state inaccessible to calling code. Only functions created inside the factory can access the closed-over variables.
"use strict";
function createAccount(openingBalance = 0) {
let balance = openingBalance;
return {
deposit(amount) {
if (!Number.isFinite(amount) || amount <= 0) {
throw new RangeError("Deposit must be a positive number");
}
balance += amount;
return balance;
},
getBalance() {
return balance;
},
};
}
const account = createAccount(100);
account.deposit(25);
console.log(account.getBalance());
console.log(Object.hasOwn(account, "balance"));
The methods share one balance binding, but the returned object has no public balance property. This pattern is useful when a small API should control all reads and writes to its state.
Modern classes also support private fields with #, but closure-based factories remain valuable when composition is more suitable than inheritance.
The module pattern and ES modules
The module pattern uses a closure to create one object with private state. The IIFE initializes that state once and returns only the public operations.
"use strict";
const idGenerator = (function () {
let nextId = 1;
return {
generate() {
const id = nextId;
nextId += 1;
return id;
},
peek() {
return nextId;
},
};
})();
console.log(idGenerator.generate());
console.log(idGenerator.generate());
console.log(idGenerator.peek());
The object literal is syntactically complete, and the binding is declared with const; no implicit global is created. Assigning to an undeclared identifier is an error in strict mode and should never be used to publish an API.
In current applications, native ES modules are usually the better file-level abstraction. Top-level module bindings are scoped to the module, and export explicitly defines its public interface.
The closure-based module pattern still fits a single pasted script, a factory-created module instance, or a deliberately isolated piece of state.
Closures and this
Closures preserve lexical variables, but regular functions do not preserve the surrounding this. For a regular function, this depends on how the function is called.
In strict mode, calling a detached regular function without a receiver gives it an undefined this. Relying on a browser global such as window is therefore incorrect and environment-dependent.
One older solution stores the method receiver in a lexical variable traditionally named self or that.
"use strict";
const profile = {
name: "Ada",
createReader() {
const self = this;
return function () {
return self.name;
};
},
};
const readName = profile.createReader();
console.log(readName());
An arrow function is the modern, more direct solution because it captures this from createReader.
"use strict";
const profile = {
name: "Ada",
createReader() {
return () => this.name;
},
};
const readName = profile.createReader();
console.log(readName());
Do not use an arrow as an object method when that method needs the object as its receiver. The arrow would inherit this from the surrounding scope instead of receiving it from profile.readName().
For a detached regular function, bind, call, or apply can set this explicitly.
"use strict";
function readName() {
return this.name;
}
const profile = { name: "Ada" };
const boundReadName = readName.bind(profile);
console.log(readName.call(profile));
console.log(boundReadName());
Lifetime and memory
A closure keeps reachable bindings alive while the closure itself remains reachable. This is necessary for persistent state, not inherently a memory leak.
Avoid retaining closures that capture large objects after they are no longer needed. Release application references and remove event listeners during cleanup so modern garbage collectors can reclaim unreachable data.
Old advice about special closure leaks in Internet Explorer’s JScript engine is historical and does not describe current JavaScript garbage collection.
Summary
An anonymous function is an unnamed function expression, often used as a callback, IIFE, or returned function. It must appear in an expression context; a bare unnamed function declaration is invalid.
A closure is any function that retains access to its lexical environment. Named functions, anonymous functions, and arrow functions can all form closures.
Use let to create per-iteration loop bindings, closure-based factories for controlled private state, and ES modules for file-level encapsulation. Choose arrows or regular functions according to the required this behavior.