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.

JavaScriptbeginner
10 min read

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:

  1. A function expression (not a declaration)
  2. Immediate invocation with ()
javascriptjavascript
// 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:

javascriptjavascript
// 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:

javascriptjavascript
// 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 patternCommon?Notes
(function() { })()Most commonStandard, everyone recognizes it
(function() { }())CommonPreferred by some style guides
(() => { })()CommonModern, concise
!function() { }()RareWorks but confusing to read
void function() { }()RareExplicitly returns undefined

Named vs Anonymous IIFEs

IIFEs can be named, which helps with debugging and stack traces:

javascriptjavascript
// 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:

javascriptjavascript
(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

javascriptjavascript
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 outside

Use Case 1: Avoiding Global Pollution

IIFEs create a private scope. Variables declared inside do not leak into the global scope:

javascriptjavascript
// 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:

javascriptjavascript
// 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:

javascriptjavascript
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:

javascriptjavascript
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:

javascriptjavascript
// 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:

javascriptjavascript
// 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

javascriptjavascript
// 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

javascriptjavascript
// 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

javascriptjavascript
// 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;
}
FeatureIIFEBlock scopeES Module
Private variablesYesYesYes
Works in <script> tagsYesPartial (var leaks)No (need type="module")
Exports public APIVia return valueNoVia export
Run onceYesYesYes (on import)
File-level scopeNo (inline)No (inline)Yes
Browser supportAll browsersES6+ES6+

When to Still Use IIFEs

Despite modern alternatives, IIFEs are still appropriate for:

  1. Legacy codebases that use var and script tags
  2. Inline scripts in HTML where you cannot use modules
  3. Async IIFEs in environments without top-level await
  4. Library wrappers that need to work in any environment
  5. Configuration objects computed once at startup
javascriptjavascript
// 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

javascriptjavascript
// SyntaxError
function () {
  console.log("oops");
}();
 
// Correct
(function () {
  console.log("works");
})();

Assuming the IIFE Name is Accessible Outside

javascriptjavascript
(function myInit() {
  console.log("running");
})();
 
myInit(); // ReferenceError: myInit is not defined

Missing Semicolons Before IIFEs

If the previous line does not end with a semicolon, the IIFE can be parsed as a function call:

javascriptjavascript
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

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/const blocks and ES modules handle most use cases
  • Still relevant: library wrappers, legacy code, inline scripts, async initialization
RunePowered by Rune AI

Frequently Asked Questions

What does IIFE stand for?

Immediately Invoked Function Expression. It is a function that is defined and called in the same statement. Pronounced "iffy" by most developers.

Can I use arrow functions for IIFEs?

Yes. `(() => { ... })()` works and is common in modern JavaScript. [Arrow functions](/tutorials/programming-languages/javascript/javascript-arrow-functions-a-complete-es6-guide) do not have their own `this`, so avoid arrow IIFEs when you need `this` to reference the calling context.

Are IIFEs outdated?

Not entirely. They are less necessary than in the pre-ES6 era because `let`, `const`, and ES modules solve most of the same problems. But they still appear in build tool output, library wrappers, polyfills, and async initialization code. Understanding IIFEs helps you read legacy code and framework source code.

Can an IIFE be async?

Yes. Prefix the function with `async` and you can use `await` inside. This is the standard pattern for using async/await in non-module [scripts](/tutorials/programming-languages/javascript/javascript-strict-mode-use-strict-explained) before top-level await became available.

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.