JS Immediately Invoked Functions (IIFE) Get Certified
An Immediately Invoked Function Expression, or IIFE (pronounced “iffy”), is a function that runs as soon as it is defined, without needing to be called separately afterward.
IIFEs were especially popular before ES6 modules existed, as a way to avoid polluting the global scope. They’re less common today, but understanding them is still useful for reading older code and for a few specific modern use cases.
In this lesson, you will learn the syntax for writing an IIFE, why developers use them, and a few variations you might encounter.
Basic IIFE Syntax
An IIFE wraps a function expression in parentheses, then immediately calls it with a second set of parentheses.
What this code does: An IIFE (Immediately Invoked Function Expression) defines a function and runs it right away.
The outer parentheses make it an expression; the final () calls it immediately.
(function () {
console.log("Application initialized.");
})();
(function () { begins a function expression. The opening parenthesis before function forces JavaScript to treat this as an expression rather than a declaration. The empty () means the function takes no parameters.console.log("Application initialized."); is the only statement inside the function. It prints a message to the console as soon as the function runs.})(); first closes the function body with }, then closes the grouping parentheses with ), and finally the trailing () immediately invokes the function. Because of this final call, the message appears right away without any separate function call later.
The outer parentheses around the function are required. Without
them, JavaScript would try to parse function as a
function declaration, which cannot be immediately invoked this
way.
Why the Parentheses Are Required
Wrapping the function in parentheses turns it into a
function expression instead of a function
declaration. Function expressions can be called immediately;
function declarations cannot be followed directly by
() in this way.
What this code does: A function declaration cannot be invoked with trailing () like that—
it causes a SyntaxError. Wrapping the function in parentheses turns it into an expression that can be called immediately.
// This causes a SyntaxError:
// function () {
// console.log("This won't work.");
// }();
console.log("Wrapping in parentheses turns it into an expression.");
// This causes a SyntaxError: is a comment that tells the reader the next few lines would fail if they were not commented out.// function () { shows the start of what looks like a function declaration. Because it is not wrapped in parentheses, the parser treats it as a declaration, which cannot be followed by an immediate call.// console.log("This won't work."); would be the body of that illegal declaration. It is also commented out so the page does not throw an error.// }(); is the closing brace followed by (). This combination is a SyntaxError when the function is written as a declaration.console.log("Wrapping in parentheses turns it into an expression."); is the only executable statement. It runs normally and prints a reminder that parentheses are what make the IIFE legal.Different IIFE Styles
There are a few equally valid ways to write the parentheses around an IIFE. Both styles below behave identically.
What this code does: Two common IIFE styles: parentheses around the whole call, or around just the function. Both run immediately and produce the same result.
(function () {
console.log("Style 1: parentheses around the whole thing.");
})();
(function () {
console.log("Style 2: parentheses around just the function.");
}());
(function () { starts the first IIFE. The outer parentheses turn the function into an expression.console.log("Style 1: parentheses around the whole thing."); prints the first style’s message as soon as the function runs.})(); closes the function, closes the grouping parentheses, and immediately invokes it with the final ().(function () { starts the second IIFE in the alternative style. Again the outer parentheses create a function expression.console.log("Style 2: parentheses around just the function."); prints the second style’s message.}()); closes the function body, then the invocation parentheses appear immediately after the closing brace, and the outer grouping parenthesis closes at the end. Both styles produce the same result.Arrow Function IIFE
An arrow function can also be used to create an IIFE, following the same wrapping pattern.
What this code does: Arrow functions can also be used as IIFEs.
The arrow function is wrapped in parentheses and invoked immediately with ().
(() => {
console.log("Arrow function IIFE running now.");
})();
(() => { starts an arrow-function IIFE. The outer parentheses are required so the arrow function is treated as an expression. The empty parameter list () means no arguments are expected.console.log("Arrow function IIFE running now."); is the single statement that runs as soon as the IIFE is invoked.})(); closes the arrow function body, closes the grouping parentheses, and the final () calls the function immediately.Passing Arguments to an IIFE
Just like any other function, an IIFE can accept parameters. The arguments are passed in the final set of parentheses that invokes it.
What this code does: IIFEs can accept arguments. Values passed in the final ()
become parameters inside the function—here, app name and version are logged on startup.
(function (appName, version) {
console.log(appName + " v" + version + " starting up...");
})("InventoryTracker", "2.1");
(function (appName, version) { declares a function expression that expects two parameters: appName and version. The outer parentheses make it an expression.console.log(appName + " v" + version + " starting up..."); concatenates the two parameters with some fixed text and prints the result. With the values that will be supplied, the output is InventoryTracker v2.1 starting up....})("InventoryTracker", "2.1"); closes the function and immediately calls it, passing the two string arguments that become appName and version.Avoiding Global Scope Pollution
One of the main historical reasons to use an IIFE is to keep variables out of the global scope, preventing them from accidentally colliding with other scripts on the same page.
What this code does: Variables declared inside an IIFE stay private.
cacheVersion is available inside the IIFE but causes a ReferenceError outside it.
(function () {
const cacheVersion = "v3";
const buildId = "20240611";
console.log("Cache version:", cacheVersion, "| Build:", buildId);
})();
try {
console.log(cacheVersion);
} catch (err) {
console.log("Error:", err.message);
}
(function () { begins an IIFE that will keep its variables private.const cacheVersion = "v3"; creates a constant that exists only inside this function. Nothing outside can see or change it.const buildId = "20240611"; is a second private value, also scoped only to the IIFE.console.log("Cache version:", cacheVersion, "| Build:", buildId); prints both private values while they are still in scope. The output is Cache version: v3 | Build: 20240611.})(); closes and immediately runs the function. After this point the private variables no longer exist.try { starts a try block so we can safely demonstrate the error that occurs when the private variable is used outside.console.log(cacheVersion); tries to read the variable that only lived inside the IIFE. This throws a ReferenceError.} catch (err) { catches the ReferenceError that was thrown on the previous line.console.log("Error:", err.message); prints the error message, confirming that cacheVersion is not defined in the outer scope.} closes the catch block.
Since cacheVersion and buildId are
declared inside the IIFE, they simply don’t exist outside of it —
there’s no risk of another script overwriting them by accident.
IIFEs That Return a Value
An IIFE can return a value, which is often used to expose only a specific, limited API while keeping other details private. This is sometimes called the module pattern.
What this code does: The classic module pattern: an IIFE returns an object with methods
that close over private data. stockLevels is hidden; only getStock and sell can access it.
const inventory = (function () {
let stockLevels = { apples: 50, bananas: 30 };
return {
getStock: function (item) {
return stockLevels[item];
},
sell: function (item, quantity) {
stockLevels[item] -= quantity;
}
};
})();
console.log(inventory.getStock("apples"));
inventory.sell("apples", 5);
console.log(inventory.getStock("apples"));
const inventory = (function () { starts an IIFE whose return value will be stored in the variable inventory.let stockLevels = { apples: 50, bananas: 30 }; creates an object that holds the actual stock counts. Because it is declared with let inside the IIFE, it is completely private.return { begins the object that will be returned. Only the methods defined inside this object will be visible to the outside world.getStock: function (item) { defines a public method that accepts an item name.return stockLevels[item]; looks up the requested item in the private stockLevels object and returns its value. The closure keeps the private data accessible only through this method.}, closes the getStock method.sell: function (item, quantity) { defines a second public method that changes the private stock.stockLevels[item] -= quantity; subtracts the sold quantity from the private stock count for that item.} closes the sell method.}; closes the object that is returned by the IIFE.})(); closes the function expression and immediately invokes it. The returned object is assigned to inventory.console.log(inventory.getStock("apples")); calls the public method and prints 50.inventory.sell("apples", 5); uses the public method to reduce the private apple count by 5.console.log(inventory.getStock("apples")); prints the new value 45, proving that the private data was changed only through the controlled API.
The stockLevels object is private, accessible only
through the two functions returned by the IIFE.
Named IIFEs
An IIFE can have a name, which is mainly useful for debugging — it will show up more clearly in stack traces and browser developer tools.
What this code does: A named IIFE can appear in stack traces, which helps with debugging. The name is local to the function and does not pollute the outer scope.
(function initializeApp() {
console.log("Named IIFE running.");
})();
(function initializeApp() { creates a function expression that has the internal name initializeApp. The name is visible only inside the function itself and is useful in stack traces.console.log("Named IIFE running."); prints a simple confirmation that the named IIFE has executed.})(); closes the function and immediately invokes it. The name initializeApp never leaks into the surrounding scope.
The name initializeApp is only accessible from
inside the function itself, since it’s still a function
expression, not a declaration.
Async IIFEs
An IIFE can also be declared with async, allowing you
to use await immediately, even outside of an
async function elsewhere in your code.
What this code does: An async IIFE lets you use await at the top level of a script
without wrapping everything in an async function. The task starts, waits briefly, then finishes.
(async function () {
console.log("Starting async task...");
await new Promise(resolve => setTimeout(resolve, 500));
console.log("Async task finished.");
})();
(async function () { starts an asynchronous function expression. The async keyword allows the use of await inside it.console.log("Starting async task..."); runs at once and prints the first message.await new Promise(resolve => setTimeout(resolve, 500)); creates a Promise that resolves after 500 milliseconds and pauses the async function until that Promise settles. This is the asynchronous part of the IIFE.console.log("Async task finished."); runs only after the 500 ms delay has finished, printing the second message.})(); closes the async function and immediately starts it. Because the function is async, the outer code continues without waiting; the delayed log appears later. Note for the Test My Code window: the delayed message may appear in the browser console (F12) rather than the output panel.IIFEs vs. Modern Alternatives
| Goal | IIFE Approach | Modern Alternative |
|---|---|---|
| Avoid polluting global scope | Wrap code in an IIFE | Use an ES6 module (import/export) |
| Private state | Module pattern via IIFE | Class with private fields, or module-scoped variables |
| Run async code immediately | Async IIFE | Top-level await (in supporting environments) |
Practical Example
Let’s use an IIFE to generate a one-time configuration object based on the current environment, without leaving any temporary variables behind in the global scope.
What this code does: A practical config setup: the IIFE computes values once and returns a config object.
Private flags like isProduction never leak into the global scope.
const config = (function () {
const isProduction = false;
const baseUrl = isProduction
? "https://api.example.com"
: "https://staging-api.example.com";
return {
baseUrl: baseUrl,
timeoutMs: isProduction ? 5000 : 15000
};
})();
console.log(config);
const config = (function () { starts an IIFE whose returned object will be stored in config.const isProduction = false; creates a private Boolean that decides which settings to use. It never leaves the IIFE.const baseUrl = isProduction begins a ternary expression that chooses the correct API URL.? "https://api.example.com" is the value used when isProduction is true.: "https://staging-api.example.com"; is the value used when isProduction is false (the case in this example).return { starts the object that will be exposed to the rest of the program.baseUrl: baseUrl, copies the chosen URL into the returned object.timeoutMs: isProduction ? 5000 : 15000 sets a different timeout depending on the same private flag (15000 ms for staging).}; closes the configuration object.})(); closes the function and runs it immediately. The temporary variables disappear; only the returned object remains.console.log(config); prints the final configuration object so you can see the values that were computed privately.Common Mistakes With IIFEs
-
Forgetting the parentheses around the function, resulting in
a
SyntaxError. -
Forgetting the final
()that actually invokes the function, which leaves it defined but never called. - Overusing IIFEs in modern projects where ES6 modules already provide proper scoping without the extra syntax.
- Trying to call an IIFE again later, forgetting that it only runs once at the moment it’s defined.
- Missing a semicolon before an IIFE on the line above it, which can occasionally cause the two lines to be interpreted together incorrectly.
Best Practices for IIFEs
- Prefer ES6 modules over IIFEs for scoping in modern JavaScript projects, when modules are available.
- Use an IIFE for quick, one-time setup logic that shouldn’t leave variables behind in the global scope.
- Name an IIFE when it’s useful for debugging or when it will appear in stack traces.
- Keep IIFEs short and focused on a single setup task.
- End the statement before an IIFE with a semicolon to avoid ambiguous parsing.
Quick Summary
- An IIFE is a function that runs immediately after it’s defined.
- Wrapping the function in parentheses turns it into an expression, which can then be invoked right away.
-
IIFEs can accept arguments, return values, and even be
declared with
async. - A common historical use case is avoiding global scope pollution and creating private state (the module pattern).
- Modern JavaScript often uses ES6 modules instead of IIFEs for these same goals.
Try It Yourself
Modify the IIFE below. Try changing the arguments passed to it, or
adding a return statement to expose a value.
What this code does: Practice example: change the username argument or add a return value, then click Test My Code to see how the IIFE runs immediately with the new input.
(function (username) {
console.log("Welcome back, " + username + "!");
})("Michael");
(function (username) { starts a function expression that expects a single parameter named username.console.log("Welcome back, " + username + "!"); builds a greeting string with the supplied name and prints it. With the argument below, the output is Welcome back, Michael!.})("Michael"); closes the function and immediately calls it, passing the string "Michael" as the value of username. Try changing the string or adding a return statement to experiment.Tip: Before reaching for an IIFE in modern code, ask whether an ES6 module would solve the same problem more simply. IIFEs still show up in older codebases and certain bundler outputs, so understanding them is worthwhile even if you rarely write one yourself.
Understanding IIFEs is useful before moving on to modules, Promises, and more advanced JavaScript concepts.
🧪 Test Your JavaScript Code
Edit the code on the left and click “Run Code” to see the console output on the right.
Click “Run Code” to see the output here.
Code Explanation
The complete example stays visible. Use Previous and Next to move through its logical blocks.
Select Next to begin exploring the code.