What is an IIFE?

Armed with an understanding of the difference between Function Declarations and Function Expressions, I want to narrow the aperture and try to answer the question, What is an IIFE?

An IIFE, or Immediately Invoked Function Expression, is best recognized as a pattern for writing a function that runs itself as soon as it is declared.

Consider the following code, which can be put into a set of <script></script> tags within an HTML file and run within a browser:

function sum() {
  return 10 + 20;
}
sum();
// expected result: 30

This code doesn't do much. I have declared a function sum() that returns the sum of 10 and 20. Below the function declaration, I call the function, allowing it to run.

This pattern is fairly straightforward, but it can be improved. For one thing, if I were to console.log(sum) after the function declaration, I would see that the sum() function exists globally within the window. This would be an issue if I only wanted to use this function once and never again, as it would be taking up global memory or polluting the global scope. Another issue here is that we have the function declaration and the function call broken out into two steps. If all we want to do is define some functionality and run it immediately, we can accomplish that using a different pattern:

(function() {
  return 10 + 20;
})();
// expected result: 30

In this second example, I have rewritten the sum() function as an anonymous function expression wrapped in a Grouping Operator, which is a pair of parentheses (). It is then followed immediately by another pair of parentheses () which calls the function expression, causing the anonymous function to run. When this function runs, it returns the sum of 10 and 20. If you put this code into your HTML file and open it in the browser, this code runs immediately. This is an example of an Immediately Invoked Function Expression.

You'll recall that a Function Expression is any valid piece of JavaScript that returns a function. The combination of the Grouping Operator and the parentheses that invokes the function inside of the Grouping Operator, ( {/* function to be returned goes here */})() is the code that will return a function. The function that is actually written inside of the Grouping Operator is the function that gets returned. But this pattern tells that function to go ahead and run.

IIFE Grammar, Syntax, and Variations

There are three main ways to write an IIFE:

  1. As a classic function expression
  2. As an arrow function expression
  3. As an async function expression

But there are also a number of alternative ways of writing and enforcing IIFEs. I'll cover as many as I can within this section.

Classic Function Expression IIFE

(function() {
  /* functionality goes here */
})();

We have seen this pattern already in our first IIFE example. We simply pass a function expression (using the function keyword) to the Grouping Operator.

Arrow Function Expression IIFE

(() => {
  /* functionality goes here */
})();

In this pattern we replace the classic function expression with an arrow function expression. Bear in mind here that arrow function expressions have some limitations – they are always anonymous and carry certain restrictions, notably around the usage of the this keyword.

Async Function Expression IIFE

// as a classic function expression
(async function() {
  /* functionality goes here */
})();

// or, as an arrow function expression
(async () => {
  /* functionality goes here */
})();

With this pattern, we simply prepend the function expression within the Grouping Operator with the keyword async. This works for both classic and arrow function expressions. Typically there will be an await statement within the function block. I will cover the benefits of this pattern later on in this post.

Named IIFEs

The IIFEs we've looked at so far have all been anonymous function expressions, but named function expressions can also be used:

(function myCoolIffe() {
  /* functionality goes here */
})();

As with regular function expressions, arrow functions cannot be given names and are always anonymous.

Passing Arguments to the IIFE

If your IIFE needs some data passed as an argument, you simply name the parameter(s) in the function expression and then pass the argument(s) within the second set of parentheses:

(function(val1, val2) {
  return val1 + val2;
})(10, 20);
// expected result: 30

Other IIFE Syntax Variations

We've observed some of the most common syntactic approaches to writing IIFEs, but there are a few other methods that are less common to be aware of. You may find these patterns lurking in projects you inherit that someone else wrote.

Parentheses Placement

The second set of parentheses can sometimes be found within the first set of parentheses:

(function() {
  /* functionality goes here */
}());

Operators

Rather than an enclosing set of parentheses, the !, ~, -, +, or void operators can prepend the function expression and enforce it:

!function() {
  /* functionality goes here */
}();

~function() {
  /* functionality goes here */
}();

-function() {
  /* functionality goes here */
}();

+function() {
  /* functionality goes here */
}();

void function() {
  /* functionality goes here */
}();

Expected Expressions

In any place where an expression is already expected, the enclosing set of parentheses is not necessary:

// within variable definition
let myCoolVariable = function() {
  /* functionality goes here */
}();

// within a logical AND operator statement
true && function() {
  /* functionality goes here */
}();

// within a comma operator statement
0, function() {
  /* functionality goes here */
}();

Defensive Semicolon

In some cases you may find a semicolon ; right before an IIFE. You may recall that in most cases semicolons are optional in JavaScript, as the compiler will handle automatic semicolon insertion for you. Some prettier or linter rules will even remove semicolons when they run. However, automatic semicolon insertion and the opening parenthesis usually found with an IIFE don't always get along:

let sum, val1, val2
sum = val1 + val2
(function() {
  /* functionality goes here */
})();
// expected result: TypeError: val2 is not a function

The compiler may parse this example as var2(function() { /* */})();, which would be an issue. So as a workaround, developers may put a semicolon ; directly before the IIFE, ensuring the code gets parsed correctly:

let sum, val1, val2
sum = val1 + val2
;(function() {
  /* functionality goes here */
})();

When to Use an IIFE

We have seen a couple of the benefits of IIFEs in action already, but let's dig into those benefits more explicitly. You should reach for the IIFE pattern if you are trying to:

  1. Run some code immediately
  2. Avoid global namespace pollution
  3. Alias global variables
  4. Write code modules that avoid naming collisions and/or have private and public methods
  5. Use the Index within for Loops
  6. Use await and for-await in older environments

Running Some Code Immediately

IIFEs excel when you want to initialize your application by running some code once and never worrying about it again.

Here's a trivial example:

(function sayHello() {
  console.log("Hi there!");
})();

This IIFE, named sayHello(), will run as soon as it is loaded into its browser or its environment. All we're doing here is greeting our user in the console, but maybe you have a tracking script you need to run as soon as possible, or you need to initialize some functionality on your website. In this case you could use an IIFE.

Here is a CodePen from a Technical Interview that I did a little while ago where I use an IIFE to initialize event listeners and handler functions for a flyout menu:

Avoiding Global Namespace Pollution

In JavaScript, when you create a function or var variable with a declaration statement outside of any other context, that function or variable gets added to the global object.

Say we want to greet our user by name:

var userName = prompt('What is your name?'); // input "Joey"

function sayHello(name) {
  console.log(`Hi there ${name}!`);
}

sayHello(userName);
// expected result: "Hi there Joey!"
console.log(window.userName);
// expected result: "Joey"
console.log(sayHello);
// expected result: function sayHello(name) { ...etc.. }

In this example we've created a var variable, userName, and assigned it the value that a user inputs to the prompt, 'What is your name?'.

After that we define a function named sayHello(), which accepts a name parameter, and greets the user by name.

Below that, we run the function, passing it the userName variable as its argument.

Finally, we console.log(window.userName) and sayHello and see that we have polluted our window scope with the userName variable and the sayHello() function.

If we're only going to run this code once and never again, it doesn't make a lot of sense to have the window carrying around a userName variable or the sayHello() function in its global scope for the entire time our application runs. Instead, we can use an IIFE and not pollute the global scope:

(function sayHello() {
  var userName = prompt('What is your name?');
  console.log(`Hi there ${userName}!`);
})();
console.log(window.userName);
// expected result: undefined
console.log(sayHello);
// expected result: ReferenceError: sayHello is not defined

Because var variables are scoped to the function that they're declared in, the userName variable only exists within the sayHello() function, which only exists within the IIFE. This is key to understanding the benefits of IIFEs: that before the introduction of let and const variables or the arrival of ES Modules and CommonJS, the IIFE enabled a developer to execute code that had a safe scope without polluting the global scope or creating an entire other function (along with a function call).

Aliasing Global Variables

This pattern also helps us to make sure we're working with the correct global variables. Say we're using a number of JavaScript libraries, specifically bling.js and jQuery. Both of these libraries alias the $ dollar sign to represent the global object. In this made-up example, if you started using the $ variable, it might be hard to determine which library you would actually be referencing.

To safely use one library or the other, you can alias the $ variable name within an IIFE:

<script
  src="https://code.jquery.com/jquery-3.6.0.js"
  integrity="sha256-H+K7U5CnXl1h5ywQfKtSj8PCmoN9aaq30gDh27Xc0jk="
  crossorigin="anonymous"></script>
<script>
  (function ($) {
    // you're safely using jQuery in here
    console.log($);
    // expected result: function ( selector, context ) {
    //   The jQuery object is actually just the init constructor 'enhanced'... etc.
    // }
  })(jQuery);
</script>

After importing jQuery from its CDN, I simply passed jQuery as an argument to the IFFE, with the $ as its parameter. Within the IIFE we are free to use the $. If we check the content of the $ with console.log($), we see that we are in fact getting jQuery.

This way, even if we are also (for some reason) using bling.js...

<script
  src="https://code.jquery.com/jquery-3.6.0.js"
  integrity="sha256-H+K7U5CnXl1h5ywQfKtSj8PCmoN9aaq30gDh27Xc0jk="
  crossorigin="anonymous"></script>
<script>
  /* bling.js */
  window.$ = document.querySelectorAll.bind(document);

  Node.prototype.on = window.on = function (name, fn) {
    this.addEventListener(name, fn);
  }

  NodeList.prototype.__proto__ = Array.prototype;

  NodeList.prototype.on = NodeList.prototype.addEventListener = function (name, fn) {
    this.forEach(function (elem, i) {
      elem.on(name, fn);
    });
  }
  console.log($);
  // expected result: querySelectorAll() { [native code ]}
  (function ($) {
    // you're safely using jQuery in here
    console.log($);
    // expected result: function ( selector, context ) {
    //   The jQuery object is actually just the init constructor 'enhanced'... etc.
    // }
  })(jQuery);
</script>

...we know that we are safely using jQuery within the IIFE, since we have explicitly aliased jQuery to the $ parameter within that IIFE. If you want to be even more explicit, you could alias jQuery to a different variable to ensure there's no confusion:

<script
  src="https://code.jquery.com/jquery-3.6.0.js"
  integrity="sha256-H+K7U5CnXl1h5ywQfKtSj8PCmoN9aaq30gDh27Xc0jk="
  crossorigin="anonymous"></script>
<script>
  (function (_) {
    // you've aliased jQuery to the underscore symbol
    console.log(_);
    // expected result: function ( selector, context ) {
    //   The jQuery object is actually just the init constructor 'enhanced'... etc.
    // }
  })(jQuery);
</script>

In this example I have aliased jQuery to the _ underscore character, so now I would be using that for the querySelector operations within the IIFE.

Modules

Prior to ES Modules, CommonJS, and other modern module solutions, IIFEs were a preferred pattern for writing code modules. The idea behind a module is that you can write all the code related to some specific functionality in one place, and import that file wherever it's needed. There are now more modern solutions for modular code, but IIFEs helped developers avoid name collisions and write public and private methods within modules in earlier programs.

Avoiding Naming Collisions

Similar to our example above with the $ variable being overloaded when we imported both bling.js and jQuery, collisions with variable and function names can also come into play when you start modularizing your code and importing third party libraries if those libraries/modules use the same variable or function names.

Say, for example, you're writing a Tax and Tip Calculator app. Inside of a calculator.js file you have three functions:

// in calcuator.js:
function applyTax(subtotal, taxRate = .08) {
  return subtotal * taxRate;
}

function applyTip(subtotal, tipRate = .25) {
  return subtotal * tipRate;
}

function calculateBill(total) {
  return total + applyTip(total) + applyTax(total + applyTip(total));
}

You can import and use this file as a basic module like so:

<script src="calculator.js"></script>
<script>
  const bill = calculateBill(100);
  console.log(bill);
  // expected result: 135
</script>

However, as your application gets larger you might find yourself adding another JavaScript library. We'll import a made up library with a file called library.js. Your HTML now might look something like this:

<script src="calculator.js"></script>
<script src="library.js"></script>
<script>
  const bill = calculateBill(100);
</script>

Great! However, what happens when libarary.js also contains a function called calculateBill?

// in library.js:
function calculateBill() {
  return "haha wrong function";
}

At this point if we try to calculate our bill...

<script src="calculator.js"></script>
<script src="library.js"></script>
<script>
  const bill = calculateBill(100);
  console.log(bill);
  // expected result: "haha wrong function"
</script>

...we just get "haha wrong function", as the calculateBill function within library.js overrides the one found in calculator.js. That's not good! And since it's a third-party library, we don't own the code and can't easily change it. Getting the maintainer of library.js to change the name of their calculateBill() function would probably be a lot of work, and might introduce breaking changes for them or other applications using their code.

To make sure we're using the correct function, let's make some adjustments to calculator.js to modularize the code. To do so, let's wrap the applyTax, applyTip, and calculateBill functions within an IIFE. We'll also create a variable named calculator and assign that IIFE as its value. Finally, the function expression will return the calculateBill function as an available method.

// in calculator.js:
var calculator = (function Calculate() {
  function applyTax(subtotal, taxRate = .08) {
    return subtotal * taxRate;
  }
  function applyTip(subtotal, tipRate = .25) {
    return subtotal * tipRate;
  }
  function calculateBill(total) {
    return total + applyTip(total) + applyTax(total + applyTip(total));
  }
  return {
    calculateBill,
  }
})();

At this point, calcuateBill is available on the calculator object as a method, and we are able to make isolated calls to calculator's calculateBill method and the calculateBill() function within library.js like so:

<script src="calculator.js"></script>
<script src="library.js"></script>
<script>
  const calculatorBill = calculator.calculateBill(100);
  console.log(calculatorBill);
  // expected result: 135

  const libraryBill = calculateBill();
  console.log(libraryBill);
  // expected result: "haha wrong function" - comes from library.js
</script>

Writing Public and Private Methods

What's neat about this pattern is that with a little bit of work we are able to more deterministically use functionality from a lot of different places, even if method names are similar or identical. Within the IIFE in calculator.js, we explicitly returned the calculateBill method. Doing so makes the calculateBill() functionality available to our broader program.

However, if we try to access one of the other functions that isn't explicitly returned from that IIFE...

<script src="calculator.js"></script>
<script src="library.js"></script>
<script>
  const appliedTax = calculator.applyTax(100);
  console.log(appliedTax);
  // expected result: TypeError: calculator.applyTax
</script>

...then we get an error telling us that applyTax isn't a function. Because we haven't explicitly returned it within the IIFE's function expression, it's not an available method. It is, however, a function that exists within the IIFE and gets used by the calculateBill method. This demonstrates how we can compose methods that are both Private (available for use within the module but nowhere else) and Public (available for use outside of the module).

Using the Index within for Loops

A little while back I had a post called Frontend JavaScript Pop Quiz, where I investigated why a for Loop wasn't working correctly.

Here's the for Loop in question:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Frontend JavaScript Pop Quiz</title>
  </head>
  <body>
    <script>
      for (var i = 0; i < 5; i++) {
        var btn = document.createElement("button");
        btn.appendChild(document.createTextNode("Button " + i));
        btn.addEventListener("click", function() {
          console.log(i);
        });
        document.body.appendChild(btn);
      }
    </script>
  </body>
</html>

After the loop appends the buttons to the page, i is set to 5, so when you click on any of the buttons (even if you're clicking on 1 or 3), the button logs out the number 5.

The solution I proposed in that post was to change var to let, and to break the event listener out, connecting it to a query selector. Also, using data- attributes. You should read that post if you're curious.

But that's an ES6 solution. Prior to ES6, we could use the IIFE pattern:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Frontend JavaScript Pop Quiz</title>
  </head>
  <body>
    <script>
      for (var i = 0; i < 5; i++) {
        var btn = document.createElement("button");
        btn.appendChild(document.createTextNode("Button " + i));
        btn.addEventListener("click", (function(copyOfI) {
          return function() {
            console.log(copyOfI);
          }
        })(i));
        document.body.appendChild(btn);
      }
    </script>
  </body>
</html>

Now when we click any of the buttons, our program logs out the correct corresponding number. This pattern isn't ideal though, as the var i variable is still globally defined. I also find returning a function within the IIFE to be kind of clunky and hard to read.

Using await and for-await in older environments

In older environments that don't have a top level await, you can wrap async / await or async / for await code patterns inside of an IIFE to make them run immediately.

Where in newer environments you are able to write:

const jsonResponse = fetch('../data/structured-data.json')
  .then(response => response.json());

export default await jsonResponse;

The same could be written using an IIFE for older environments:

var getJsonResponse = async function(endpoint) {
  /* implement fetch here */
};

(async function() {
  var jsonResponse = await getJsonResponse('../data/structured-data.json');
  for await (var data of jsonResponse) {
    console.log(data);
  }
})();

IIFE Summary

So what is an IIFE? An Immediately Invoked Function Expression, or IIFE, is a pattern used to run some code immediately and allows a developer to declare variables and functions that don't pollute the global namespace.

An IIFE can be written as a classic function expression, an arrow function expression, or an async function expression. They can be named or anonymous, be given arguments, and have their second set of parentheses either inside or outside of the first set. Alternate IIFE grammar patterns can also use certain JavaScript operators, or pass the IIFE to a place where an expression is expected to enforce the IIFE. Finally, it's common practice to place ; a semicolon in front of an IIFE. This helps avoid any problems that may result from automatic semicolon insertion.

IIFEs are beneficial when you need to run some code immediately, avoid global scope and namespace pollution, create a module pattern with private and public variables and methods, ensure your for Loop index refers to the correct iteration, and immediately use await and for-await in older environments that don't have a top-level await available.

IIFEs are sort of a dated practice now that ES6, ES Modules, Common JS, Private Class Methods, and many other newer language features have broad support from browsers and Node. However, understanding IIFEs is not only good for understanding the problems that these newer patterns solve, but if you work in older code bases or open source projects, you're bound to run into an IIFE. Hopefully this post will help you understand what that IIFE has been helping its project accomplish.

Sources / Further Reading