JavaScript IIFE: Immediately Invoked Functions
Learn what an IIFE is in JavaScript and why it matters. Covers IIFE syntax, use cases, avoiding global pollution, module patterns, async IIFEs, and when to use alternatives like ES modules and block scoping.
An IIFE (Immediately Invoked Function Expression) is a function that runs as soon as it is defined. It creates a private scope, prevents global variable pollution, and executes setup code without leaving anything behind. Despite being less common in modern JavaScript with ES modules and block scoping, IIFEs remain valuable for specific patterns that every developer should understand.
What is an IIFE?
IIFE stands for Immediately Invoked Function Expression. It combines two things:
- A function expression (not a declaration)
- Immediate invocation with
()
// Standard IIFE syntax
(function () {
console.log("This runs immediately!");
})();
// Arrow function IIFE
(() => {
console.log("Arrow IIFE runs immediately too!");
})();The outer parentheses (function() { ... }) turn the function declaration into an expression. The trailing () immediately calls that expression.
Why the Parentheses?
Without the wrapping parentheses, JavaScript treats function as a declaration, not an expression, and a declaration cannot be immediately invoked:
// SyntaxError: unexpected token '('
function () {
console.log("This fails");
}();
// Works: parentheses make it an expression
(function () {
console.log("This works");
})();IIFE Syntax Variations
All of these produce the same result:
// Most common: wrapping parentheses with trailing ()
(function () {
console.log("Pattern 1");
})();
// Alternative: calling parentheses inside wrapper
(function () {
console.log("Pattern 2");
}());
// Arrow function IIFE
(() => {
console.log("Pattern 3");
})();
// Using unary operators (less common)
!function () {
console.log("Pattern 4");
}();
void function () {
console.log("Pattern 5");
}();| Syntax pattern | Common? | Notes |
|---|---|---|
(function() { })() | Most common | Standard, everyone recognizes it |
(function() { }()) | Common | Preferred by some style guides |
(() => { })() | Common | Modern, concise |
!function() { }() | Rare | Works but confusing to read |
void function() { }() | Rare | Explicitly returns undefined |
Named vs Anonymous IIFEs
IIFEs can be named, which helps with debugging and stack traces:
// Anonymous IIFE
(function () {
// name in stack trace: (anonymous)
})();
// Named IIFE
(function initializeApp() {
// name in stack trace: initializeApp
// the name is NOT accessible outside the IIFE
})();
// Named arrow IIFEs are not possible
// Arrow functions cannot have names (only assigned to variables)Name Your IIFEs
Named IIFEs produce better stack traces when errors occur. The name exists only inside the IIFE and does not pollute the outer scope. Use descriptive names like initializeApp or setupEventListeners.
IIFE with Parameters
IIFEs accept arguments just like any other function:
(function (greeting, name) {
console.log(`${greeting}, ${name}!`);
})("Hello", "World");
// Output: "Hello, World!"
// Passing global objects for local aliasing
(function ($, win) {
// $ is guaranteed to be jQuery here
// win is guaranteed to be window
$.ajax({ url: "/api" });
})(jQuery, window);IIFE with Return Value
const config = (function () {
const env = "production";
const apiUrl = env === "production"
? "https://api.example.com"
: "http://localhost:3000";
return {
env,
apiUrl,
timeout: 5000,
};
})();
console.log(config.apiUrl); // "https://api.example.com"
console.log(config.env); // "production"
// env and apiUrl variables are NOT accessible outsideUse Case 1: Avoiding Global Pollution
IIFEs create a private scope. Variables declared inside do not leak into the global scope:
// WITHOUT IIFE: pollutes global scope
var counter = 0;
var maxCount = 10;
function increment() { counter++; }
// counter, maxCount, and increment are all global
// WITH IIFE: nothing leaks
(function () {
var counter = 0;
var maxCount = 10;
function increment() { counter++; }
// counter, maxCount, and increment are private
})();
console.log(typeof counter); // "undefined"This was critical before let and const existed, because var does not have block scope:
// var leaks out of blocks
if (true) {
var leaked = "I am global!";
}
console.log(leaked); // "I am global!"
// IIFE contains var
(function () {
if (true) {
var contained = "I am private!";
}
})();
console.log(typeof contained); // "undefined"Use Case 2: The Module Pattern
Before ES modules, IIFEs were the standard way to create modules with public and private members:
const Counter = (function () {
// Private variables
let count = 0;
// Private function
function validate(n) {
return typeof n === "number" && !isNaN(n);
}
// Public API (returned object)
return {
increment() {
count++;
return count;
},
decrement() {
if (count > 0) count--;
return count;
},
reset() {
count = 0;
return count;
},
getCount() {
return count;
},
addAmount(n) {
if (validate(n)) count += n;
return count;
},
};
})();
Counter.increment(); // 1
Counter.increment(); // 2
Counter.addAmount(10); // 12
Counter.decrement(); // 11
Counter.getCount(); // 11
console.log(Counter.count); // undefined (private!)Revealing Module Pattern
A variation where you define everything as private and then reveal selected members:
const UserService = (function () {
let users = [];
function findById(id) {
return users.find((u) => u.id === id);
}
function add(name, email) {
const user = { id: Date.now(), name, email };
users = [...users, user];
return user;
}
function remove(id) {
users = users.filter((u) => u.id !== id);
}
function getAll() {
return [...users]; // return copy, not reference
}
// Reveal only what should be public
return { add, remove, getAll, findById };
})();Use Case 3: Loop Variable Capture
A classic JavaScript problem with var in loops:
// BUG: all buttons alert "3"
for (var i = 0; i < 3; i++) {
document.getElementById(`btn-${i}`).addEventListener("click", function () {
alert(i); // always 3 because var is function-scoped
});
}
// FIX with IIFE: captures the current value of i
for (var i = 0; i < 3; i++) {
(function (capturedI) {
document.getElementById(`btn-${capturedI}`).addEventListener("click", function () {
alert(capturedI); // 0, 1, 2 (correct!)
});
})(i);
}
// Modern fix: use let (block-scoped)
for (let i = 0; i < 3; i++) {
document.getElementById(`btn-${i}`).addEventListener("click", function () {
alert(i); // 0, 1, 2 (correct with let)
});
}Use Case 4: Async IIFE
Before top-level await was available, IIFEs were the way to use async/await at the top level:
// Cannot use await at the top level in older environments
// const data = await fetch('/api'); // SyntaxError in older JS
// Async IIFE
(async function () {
try {
const response = await fetch("/api/users");
const users = await response.json();
console.log(users);
} catch (error) {
console.error("Failed to fetch users:", error);
}
})();
// Arrow function async IIFE
(async () => {
const data = await fetch("/api/config").then((r) => r.json());
console.log(data);
})();Top-Level Await
Modern JavaScript (ES2022+) supports top-level await in ES modules. If your environment supports it, you may not need async IIFEs. However, async IIFEs remain useful in non-module scripts and older codebases.
Use Case 5: One-Time Initialization
// Initialize application state once
const app = (function () {
// Setup runs once
const startTime = Date.now();
const id = crypto.randomUUID();
const features = {
darkMode: window.matchMedia("(prefers-color-scheme: dark)").matches,
touchScreen: "ontouchstart" in window,
language: navigator.language,
};
return {
getUptime() {
return Date.now() - startTime;
},
getId() {
return id;
},
getFeatures() {
return { ...features };
},
};
})();IIFEs vs Modern Alternatives
Block Scoping with let/const
// IIFE for scope isolation (old pattern)
(function () {
var temp = computeValue();
doSomethingWith(temp);
})();
// Block scope with let/const (modern)
{
const temp = computeValue();
doSomethingWith(temp);
}ES Modules
// IIFE module pattern (old)
const MyModule = (function () {
let privateState = 0;
return { getState: () => privateState };
})();
// ES module (modern)
// myModule.js
let privateState = 0;
export function getState() {
return privateState;
}| Feature | IIFE | Block scope | ES Module |
|---|---|---|---|
| Private variables | Yes | Yes | Yes |
Works in <script> tags | Yes | Partial (var leaks) | No (need type="module") |
| Exports public API | Via return value | No | Via export |
| Run once | Yes | Yes | Yes (on import) |
| File-level scope | No (inline) | No (inline) | Yes |
| Browser support | All browsers | ES6+ | ES6+ |
When to Still Use IIFEs
Despite modern alternatives, IIFEs are still appropriate for:
- Legacy codebases that use
varand script tags - Inline scripts in HTML where you cannot use modules
- Async IIFEs in environments without top-level await
- Library wrappers that need to work in any environment
- Configuration objects computed once at startup
// Library wrapper that works everywhere
(function (root, factory) {
if (typeof module === "object" && module.exports) {
module.exports = factory(); // CommonJS
} else if (typeof define === "function" && define.amd) {
define(factory); // AMD
} else {
root.MyLibrary = factory(); // Browser global
}
})(typeof self !== "undefined" ? self : this, function () {
// Library code here
return { version: "1.0.0" };
});Common Mistakes
Forgetting the Wrapping Parentheses
// SyntaxError
function () {
console.log("oops");
}();
// Correct
(function () {
console.log("works");
})();Assuming the IIFE Name is Accessible Outside
(function myInit() {
console.log("running");
})();
myInit(); // ReferenceError: myInit is not definedMissing Semicolons Before IIFEs
If the previous line does not end with a semicolon, the IIFE can be parsed as a function call:
const x = 42
(function () {
console.log("IIFE");
})();
// Interpreted as: const x = 42(function() { ... })()
// TypeError: 42 is not a function
// Fix: use a leading semicolon
const x = 42;
(function () {
console.log("IIFE");
})();Rune AI
Key Insights
- IIFE = function expression + immediate call:
(function() { })()or(() => { })() - Creates private scope: variables inside do not leak out
- Module pattern: return an object to expose a public API with private internals
- Async IIFE:
(async () => { await ... })()for top-level async in non-module scripts - Modern alternatives:
let/constblocks and ES modules handle most use cases - Still relevant: library wrappers, legacy code, inline scripts, async initialization
Frequently Asked Questions
What does IIFE stand for?
Can I use arrow functions for IIFEs?
Are IIFEs outdated?
Can an IIFE be async?
Conclusion
An IIFE is a function expression that runs the moment it is defined. Wrap any function in parentheses and add () at the end to make it an IIFE. The primary benefit is scope isolation - variables declared inside an IIFE do not leak into the surrounding scope. While modern JavaScript offers alternatives like let/const block scoping and ES modules, IIFEs remain useful for async initialization, library wrappers, legacy codebases, and one-time setup code. Learn to recognize the pattern because it appears throughout JavaScript libraries, build output, and older codebases.
More in this topic
OffscreenCanvas API in JS for UI Performance
Master the OffscreenCanvas API to offload rendering from the main thread. Covers worker-based 2D and WebGL rendering, animation loops inside workers, bitmap transfer, double buffering, chart rendering pipelines, image processing, and performance measurement strategies.
Advanced Web Workers for High Performance JS
Master Web Workers for truly parallel JavaScript execution. Covers dedicated and shared workers, structured cloning, transferable objects, SharedArrayBuffer with Atomics, worker pools, task scheduling, Comlink RPC patterns, module workers, and performance profiling strategies.
JavaScript Macros and Abstract Code Generation
Master JavaScript code generation techniques for compile-time and runtime metaprogramming. Covers AST manipulation, Babel plugin authorship, tagged template literals as macros, code generation pipelines, source-to-source transformation, compile-time evaluation, and safe eval alternatives.