JavaScript IIFE Immediately Invoked Functions
An IIFE is a function that runs the moment it is defined. Learn the syntax, why it was essential before ES6 modules, and where IIFEs still make sense today.
An IIFE (Immediately Invoked Function Expression) is a function that runs the instant it is defined. You write it, wrap it in parentheses, and call it immediately. It executes once and never again.
(function() {
const message = "I run immediately!";
console.log(message);
})();
// "I run immediately!"The function is defined and called in a single expression. No separate invocation line. No stored reference. It runs and is done.
The Syntax
An IIFE always has the same two pieces: a function expression, and a trailing pair of parentheses that calls it immediately. There are two common ways to arrange the wrapping parentheses:
// Form 1: wrapping parentheses on the outside
(function() {
console.log("IIFE 1");
})();
// Form 2: wrapping parentheses on the inside (Crockford style)
(function() {
console.log("IIFE 2");
}());Arrow functions can be used the same way, and the wrapping parentheses work identically, since the parentheses are what matter for parsing, not the specific function syntax inside them:
(() => {
console.log("Arrow IIFE");
})();The outer parentheses are what make this work. Without them, the function keyword at the start of a line is parsed as a function declaration, and declarations cannot be immediately called:
// SyntaxError: function declarations require a name
function() {
console.log("This fails");
}();The parentheses turn the declaration into an expression, which can then be called.
Why IIFEs Existed
Before ES6 (2015), JavaScript had only two scopes: global and function. There was no let, no const, no block scope, and no modules. If you loaded multiple scripts, all their var declarations landed in the same global scope and overrode each other.
IIFEs solved this by wrapping entire scripts in a function scope. Two separate script files can each declare a variable named count without colliding, because each one is trapped inside its own IIFE:
// Script A
(function() {
var count = 0; // Not global -- scoped to this IIFE
window.moduleA = { increment() { return ++count; } };
})();A second, completely unrelated script can reuse the same variable name without any conflict, since it lives in its own separate IIFE scope:
// Script B
(function() {
var count = 100; // Different count -- different scope
window.moduleB = { get() { return count; } };
})();Only the deliberately exposed moduleA and moduleB objects reach the global scope. The count variables themselves never do, since each one only exists inside the function that declared it:
console.log(moduleA.increment()); // 1
console.log(moduleB.get()); // 100
console.log(typeof count); // "undefined" -- not globalEach IIFE created a private scope. Variables stayed local. The global namespace stayed clean.
Modern Alternatives
Most IIFE use cases are now solved by native language features:
| IIFE Use Case | Modern Alternative |
|---|---|
| Scope isolation | let/const + block scope {} |
| Module pattern | ES modules: export/import |
| Avoiding global variables | ES modules: top-level is module-scoped |
| Loop closure trap | let in for loops |
// Old: IIFE for scope isolation
(function() {
var temp = "isolated";
// ...
})();
// New: block scope
{
const temp = "isolated";
// ...
}
// temp is not accessible hereWhere IIFEs Still Make Sense
The Module Pattern
When you want a single private scope without a separate file, an IIFE that returns an object of methods gives you a private history array with a public API around it:
const Calculator = (function() {
const history = []; // Private
return {
add(a, b) {
const result = a + b;
history.push({ op: "add", a, b, result });
return result;
},
getHistory() { return [...history]; },
};
})();The history array itself is never exposed, only the results of calling the returned methods, which is the same encapsulation guarantee the closures-based module pattern gives you without an IIFE:
console.log(Calculator.add(2, 3)); // 5
console.log(Calculator.history); // undefinedAsync Top-Level Code
Top-level await is supported in ES modules today, but in a plain script or an older environment without module support, an async IIFE gives you the same ability to await promises immediately:
(async () => {
const response = await fetch("/api/data");
const data = await response.json();
console.log(data);
})();Isolated One-Off Execution
When you need to run code without leaving variables behind, wrapping a small computation in an IIFE keeps every intermediate variable local to that block:
// Process data without polluting scope
const result = (() => {
const temp = expensiveComputation();
const filtered = temp.filter(isValid);
return filtered.map(transform);
})();The temp and filtered variables disappear after the IIFE runs. Only result remains.
Avoiding var Hoisting in Legacy Code
When you must work with var in an older codebase:
// Without IIFE: i leaks
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 3, 3, 3
}
// With IIFE: each callback gets its own copy
for (var i = 0; i < 3; i++) {
((j) => {
setTimeout(() => console.log(j), 100); // 0, 1, 2
})(i);
}The modern fix is let, but IIFEs remain the fix when you cannot change var to let.
Named IIFEs
You can give an IIFE a name for better stack traces:
(function initializeApp() {
console.log("App started");
throw new Error("Something went wrong");
})();
// Stack trace includes "initializeApp" instead of "anonymous"The name is only visible inside the IIFE body. It does not leak to the outer scope.
For the module pattern in depth, see Practical Use Cases for JS Closures in Real Apps. For scope fundamentals, see JavaScript Function Scope Local vs Global Scope.
Rune AI
Key Insights
- An IIFE is a function that is defined and immediately called: (function() { ... })();.
- IIFEs create an isolated scope, preventing variable leaks into the global scope.
- They were the primary way to create modules before ES6 import/export existed.
- Modern alternatives include block scope with let/const and ES modules.
- IIFEs are still useful for the module pattern, async wrappers, and one-off isolated execution.
Frequently Asked Questions
Are IIFEs still necessary with ES6 modules?
Why do some IIFEs start with a semicolon?
Can async IIFEs exist?
Conclusion
IIFEs were the pre-ES6 solution for scope isolation, data privacy, and the module pattern. Modern JavaScript has modules and block-scoped variables for most of those use cases. But IIFEs still shine for isolated execution, async top-level code, and the classic module pattern when you need a single private scope.
More in this topic
JavaScript While Loop Explained: A Complete Guide
A while loop repeats code as long as a condition stays true. Learn the syntax, see practical examples, and understand when a while loop is the right choice over a for loop.
JavaScript Loops Tutorial: for, while, do while
Loops let JavaScript repeat code without writing it twice. Learn the three core loop types, what each one does, and when to choose one over another.
How to Loop Through Arrays Using JS for Loops Guide
Looping through arrays is the most practical use of JavaScript for loops. Learn how to access each element by index, avoid off-by-one errors, and choose between for, for...of, and forEach.