· By John Kavanagh

Immediately Invoked Function Expressions (IIFEs)

Abstract image used to represent JavaScript IIFEs Explained
Image by Scott Rodgerson.

In JavaScript, functions are firstclass citizens, meaning that functions can be assigned to variables, passed as arguments to other functions, and returned as values from functions. Functions can also be declared and invoked at the same time, creating what is known as an Immediately Invoked Function Expression (IIFE).

IIFE is a useful design pattern for creating private scopes and avoiding variable collisions in JavaScript code. This pattern has become very popular in modern JavaScript development, particularly when working with modules. In this article, we will discuss the basics of IIFE, advanced code examples that can be used in realworld development, compare IIFE with other strategies, and cover potential issues that developers may come across whilst working with modules.


Basic Example of IIFE

Let's kick things off with a basic IIFE. The following function takes two parameters and logs their sum:

(function (x, y) {
  console.log(x + y);
})(2, 3);  //=> 5

Here, the function is wrapped in parentheses and immediately invoked with two arguments: 2 and 3. The result is logged to the console.

The IIFE pattern can also be used to create a private scope for variables. Consider this example:

(function () {
  const x = 1;
  console.log(x);
})();  //=> 1

In this example, the variable x is declared inside the IIFE and is not accessible outside the function. This creates a private scope for the variable.


Advanced Examples of IIFE

IIFE can be used in many advanced scenarios in realworld development. Here are some examples:

Example 1: Creating a Module

IIFE is often used to create a module in JavaScript. A module is a selfcontained block of code that can be easily reused and maintained. In this example, we will create a module that contains a function to calculate the area of a circle:

const circle = (function () {
  const pi = 3.14;
  function calculateArea(radius) {
    return pi * radius * radius;
  }
  return {
    calculateArea: calculateArea,
  };
})();

console.log(circle.calculateArea(5));  //=> 78.5

Here, an IIFE is used to create a module called circle. The module contains a constant pi and a function calculateArea which takes the radius of a circle as input and returns its area. The calculateArea function is returned as a property of the module. The module can be accessed using the circle variable and the calculateArea function can be invoked using circle.calculateArea(5).

Example 2: Using Arrow Functions

Arrow functions were introduced in ECMAScript 6 and provide a more concise syntax for writing functions. Arrow functions can also be used inside IIFE to create more readable code. For example:

const sum = ((x, y) => x + y)(2, 3);
console.log(sum);  //=> 5

Here, the arrow function returns x + y, and the immediate call passes in 2 and 3. Its return value is assigned to sum, then logged separately. Unlike the first example, this one keeps the calculated value for later use.

Example 3: Using Destructuring

Destructuring is a feature introduced in ECMAScript 6 that allows us to extract values from objects and arrays. Destructuring can be used inside IIFE to make the code even more readable and concise. Here's an example:

const user = {
  name: 'John Doe',
  age: 30,
  email: 'john.doe@example.com',
};

(function ({ name, age, email }) {
  console.log(`${name} is ${age} years old and has an email ${email}.`);
})(user);  //=> John Doe is 30 years old and has an email john.doe@example.com.

In this example, destructuring is used inside the IIFE to extract the name, age, and email properties from the user object.


Comparison with Other Strategies

IIFE is just one of the many strategies that can be used to create private scopes in JavaScript. Another strategy is to use the let and const keywords to declare variables inside a block. Consider the following:

{
  const x = 1;
  console.log(x);
}  //=> 1

The variable x is declared inside a block and is not accessible outside the block. This creates a private scope for the variable.

A block can keep let and const bindings private, and a function created inside it can retain access to them through a closure. An IIFE is a convenient alternative when you want an expression that creates the private scope and returns a public API immediately, as the circle example does.


Potential Issues

When using IIFE, developers may come across issues related to variable scoping and memory usage:

Variable Scoping

In a classic script without strict mode, assigning to a name that has not been declared can create a property on the global object. The IIFE does not prevent that mistake. Declaring the local variable with var, let or const keeps its scope explicit.

Here is a deliberately broken nonstrict example, assuming neither x nor y already exists globally. It logs y before catching the expected error from trying to read the private x:

// Deliberately broken: run as a classic, non-strict script.
(function () {
  var x = 1;
  y = 2;  // Missing declaration creates an accidental global here.
})();

console.log(y);  //=> 2

try {
  console.log(x);
} catch (error) {
  console.log(error.name);  //=> ReferenceError
}

Here, y = 2 creates a global property because this classic script is nonstrict and y has not been declared. In strict mode, the assignment itself throws a ReferenceError; ECMAScript modules are always strict. Add the appropriate declaration rather than relying on an accidental global. The try/catch only lets this demonstration show the separate scope error for x.

Memory Usage

Each IIFE call creates an execution context and local bindings, just as an ordinary function call does. Those bindings normally become unreachable when the call returns unless a closure or another reachable object retains them. Consider the following code snippet:

for (var i = 0; i < 1000000; i++) {
  (function () {
    var x = i;
    // do some work with x
  })();
}

Admittedly it is an extreme example, but here the IIFE is called 1,000,000 times. Each call creates temporary bindings; this is execution and allocation work, not a leak, provided nothing reachable retains those bindings after the call returns.

Assigning a local primitive to null does not provide useful manual garbage collection. The local binding becomes unreachable when the call returns, so the equivalent example is:

for (var i = 0; i < 1000000; i++) {
  (function () {
    var x = i;
    // do some work with x
    // x becomes unreachable after this call returns
  })();
}

In an extension of this, developers should be mindful of the performance impact of using IIFEs excessively; whilst they can be a useful tool in certain situations (such as encapsulating code and preventing variable collisions), excessive use of IIFEs can lead to decreased performance due to the additional overhead of creating a new function scope for each call.

As with any programming tool, it's important to use IIFEs carefully and with consideration for their potential impact on performance and memory usage.


The Wrap‑up

Invoked Function Expressions (IIFE) are a powerful tool in JavaScript for encapsulating code and preventing variable collisions. By defining and immediately invoking a function, developers can create a private scope where variables and functions are kept separate from the global scope.

However, as with any programming tool, there are potential issues to be aware of when using IIFEs. Variable scoping and memory usage can be a concern if not managed properly, and excessive use of IIFEs can lead to decreased performance due to the additional overhead of creating a new function scope for each call.

Despite these considerations, IIFEs remain useful when code needs a selfcontained scope. Use const and let to declare bindings correctly, and release any external references, listeners or closures that genuinely retain state; local bindings need no manual null assignment when the call returns.


Looking for technical direction?

I support teams that need senior judgement on React, Next.js, headless CMS architecture, performance, migrations, and technical SEO.