IIFE: The Pattern You Should Know

Posted by

Why Immediately Invoked Function Expressions still matter in modern JavaScript.

Why Immediately Invoked Function Expressions still matter in modern JavaScript.

Introduction

Before ES6 modules, IIFEs (Immediately Invoked Function Expressions) were everywhere. They powered jQuery plugins, AngularJS, and even big frameworks like React in its early days. Today, with import/export and let/const, many devs think IIFEs are dead.

But here’s the truth: IIFEs are still a core pattern worth knowing. They help you:

  • Encapsulate logic and variables.
  • Avoid polluting global scope.
  • Execute code immediately with a private context.
  • Build self-contained utilities and polyfills.

This article breaks down what an IIFE is, how it works, why it was popular, and when you should still use it in 2025.


1) What is an IIFE?

An IIFE is a function that’s defined and immediately executed.

(function() {
console.log("IIFE runs instantly!");
})();
  • “Immediately Invoked” → runs as soon as it’s defined.
  • “Function Expression” → wrapped in parentheses so JS treats it as an expression, not a declaration.

👉 Without parentheses, this would be a syntax error, because function foo() {} by itself is a declaration, not an expression.


2) Anatomy of an IIFE

Basic Form

(function() {
// private scope
const msg = "hidden";
console.log("IIFE executed");
})();

Arrow Function Version

(() => {
console.log("Arrow IIFE");
})();

With Parameters

((name) => {
console.log("Hello " + name);
})("Alice");

👉 You can pass values in just like any function call.


3) Why Did We Use IIFEs So Much?

Before ES6 introduced block scope (let, const) and modules (import/export), developers had only:

  • Global scope
  • Function scope (via var)

That meant variables leaked into the global object unless wrapped. IIFEs were the go-to solution.

Example: Avoiding Global Pollution

var count = 0;

(function() {
var count = 10; // scoped to IIFE
console.log("Inside:", count); // 10
})();

console.log("Outside:", count); // 0

4) IIFEs as Module Pattern

IIFEs formed the basis of the module pattern, long before ES modules existed.

const Counter = (function() {
let count = 0; // private variable

return {
inc() { count++; return count; },
dec() { count--; return count; },
get() { return count; }
};
})();

console.log(Counter.inc()); // 1
console.log(Counter.get()); // 1
console.log(Counter.count); // undefined (hidden!)

👉 This gave us private state — a precursor to today’s ES6 modules and private class fields.


5) Types of IIFEs

Classic

(function() { console.log("classic"); })();

Unary Operator

!function() { console.log("with ! operator"); }();

Arrow

(() => console.log("arrow iife"))();

👉 All achieve the same thing. Wrapping makes the function an expression, so it can be invoked immediately.


6) Real-World Uses Today

Even in 2025, IIFEs are handy:

a) Polyfills / Shims

(function() {
if (!Array.prototype.first) {
Array.prototype.first = function() {
return this[0];
};
}
})();

Runs once, patches global objects, and avoids leaking variables.


b) Initializing Libraries

const Config = (() => {
const apiKey = "SECRET";
return { apiKey };
})();

console.log(Config.apiKey); // SECRET

Keeps config private and immutable.


c) Async Setup

(async () => {
const data = await fetch("/api/data").then(r => r.json());
console.log("Loaded:", data);
})();

👉 Great for top-level async logic, especially in scripts before top-level await existed. Still useful when you want local async blocks inside a function.


d) Preventing Variable Collisions in Global Scripts

If you’re embedding JS in legacy apps, CMS templates, or <script> tags, IIFEs prevent name collisions.

(function() {
const $ = document.querySelector.bind(document);
const btn = $("#save");
btn.addEventListener("click", () => console.log("Saved!"));
})();

7) IIFE vs Modern Alternatives

  • Block scope (let, const): Many old IIFE use cases (like scoping loop variables) are solved by let.
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1000);
}
  • Modules (import/export): ES modules isolate scope automatically—no need for IIFEs to hide variables.
// utils.js
export function greet(name) { return "Hello " + name; }
  • Classes with private fields: Replace IIFEs for encapsulation.
class Counter {
#count = 0;
inc() { return ++this.#count; }
}

👉 Still, IIFEs are lightweight, framework-free, and work everywhere, including scripts without bundlers.


8) Gotchas

  • Debugging: Stack traces show (anonymous) unless you name the function.
  • Readability: Arrow IIFEs with heavy nesting can confuse newcomers.
  • Performance: Negligible overhead, but avoid IIFEs in hot loops.

9) Quick Reference Table


Conclusion

IIFEs may feel “old school,” but they remain a powerful, universal pattern:

  • They isolate scope, prevent pollution, and enable private state.
  • They’re still useful for polyfills, quick scripts, async bootstrapping, and library setup.
  • Modern JS has alternatives (modules, let, private fields), but knowing IIFEs helps you understand the history and mechanics of today’s patterns.

Pro tip: When you see (() => { ... })() in code, don’t panic—it’s just an IIFE creating a temporary sandbox.


Call to Action

Have you used an IIFE recently — in a legacy project, a quick <script>, or async setup?

💬 Share your use case in the comments.
🔖 Bookmark this as your IIFE cheat sheet.
👩‍💻 Share with a teammate who still thinks IIFEs died with jQuery.

Leave a Reply

Your email address will not be published. Required fields are marked *