IIFE & Function Patterns

Difficulty: Intermediate

An Immediately Invoked Function Expression (IIFE, pronounced 'iffy') is a function that is defined and executed at the same time. The syntax involves wrapping a function expression in parentheses and then immediately calling it with another pair of parentheses. The outer parentheses are necessary to tell the JavaScript parser to treat the function as an expression rather than a declaration. Without them, the parser would see the `function` keyword and expect a function declaration, which cannot be immediately invoked.

IIFEs were historically one of the most important patterns in JavaScript because they solved a critical problem: global scope pollution. Before ES6 modules and block-scoped variables (let/const), all variables declared with `var` at the top level became global properties. In large applications with multiple scripts loaded via script tags, this led to naming collisions and difficult-to-debug bugs. IIFEs create a new function scope, isolating all variables declared inside from the global scope.

The module pattern builds directly on IIFEs. By returning an object from an IIFE, you expose a public API while keeping internal implementation details private. This was the standard way to create modular, encapsulated code in JavaScript before native ES6 module support. Libraries like jQuery, Underscore, and Lodash were all structured as IIFEs that exposed a single global variable.

IIFEs can be either named or anonymous. Anonymous IIFEs are more common, but named IIFEs offer benefits: the name appears in stack traces for easier debugging, and the function can reference itself for recursion. In modern JavaScript, the primary reasons to use IIFEs have diminished significantly. ES6 modules provide proper encapsulation with import/export, and block-scoped variables (let/const) eliminate the global pollution problem within blocks. However, IIFEs still appear in legacy codebases, bundler output, and specific scenarios like creating block-scoped variables in contexts where block scoping is not available.

Function composition is another important pattern that builds on the concept of functions as values. Composition means combining two or more functions to produce a new function where the output of one function becomes the input of the next. The compose function (right to left) and pipe function (left to right) are utility functions that automate this chaining. This pattern is fundamental in functional programming and appears in libraries like Ramda, lodash/fp, and Redux middleware. Understanding composition helps you write small, focused, reusable functions that can be combined to solve complex problems.

Code examples

IIFE Syntax and Variations

// Standard IIFE
(function() {
  const secret = 'hidden from global scope';
  console.log('IIFE executed:', secret);
})();

// IIFE with parameters
(function(name, version) {
  console.log(`${name} v${version}`);
})('MyApp', '1.0');

// Arrow function IIFE
(() => {
  console.log('Arrow IIFE executed');
})();

// Named IIFE (useful for debugging and recursion)
(function factorial(n) {
  if (n <= 1) return 1;
  return n * factorial(n - 1);
})(5); // result: 120

// IIFE returning a value
const config = (function() {
  const env = 'production';
  const apiUrl = 'https://api.example.com';
  return { env, apiUrl };
})();
console.log(config);

IIFEs are wrapped in parentheses to make the parser treat them as expressions. They can take parameters, use arrow syntax, be named for recursion, and return values that get assigned to variables.

Avoiding Global Pollution with IIFEs

// Without IIFE -- pollutes global scope
var counter = 0;  // global!
var utils = {};    // global!

// With IIFE -- everything stays private
const MyLibrary = (function() {
  // Private -- not accessible outside
  var internalCounter = 0;
  var cache = {};

  function privateHelper(key) {
    return cache[key] || null;
  }

  // Public API
  return {
    increment() {
      return ++internalCounter;
    },
    getCount() {
      return internalCounter;
    },
    store(key, value) {
      cache[key] = value;
    },
    retrieve(key) {
      return privateHelper(key);
    }
  };
})();

MyLibrary.store('user', 'Alice');
console.log(MyLibrary.retrieve('user'));
console.log(MyLibrary.increment());
console.log(MyLibrary.increment());
console.log(MyLibrary.getCount());

// Private members are inaccessible
console.log(typeof internalCounter); // undefined
console.log(typeof cache);           // undefined

The IIFE creates a private scope. Only the returned object is exposed as MyLibrary. The internal counter, cache, and helper function are completely hidden. This was the standard library pattern before ES6 modules.

Modern Alternatives to IIFE

// ES6 Modules (the modern replacement for IIFE module pattern)
// file: mathUtils.js
// let internalState = 0; // module-scoped, not global
// export function add(a, b) { return a + b; }
// export function multiply(a, b) { return a * b; }

// Block scoping with let/const replaces IIFE for isolation
{
  const blockScoped = 'only accessible in this block';
  let counter = 0;
  counter++;
  console.log('Block:', blockScoped, counter);
}
// console.log(blockScoped); // ReferenceError

// But IIFE is still useful for async initialization
const db = await (async function() {
  // Simulate async initialization
  const connection = { host: 'localhost', ready: true };
  console.log('Database initialized');
  return connection;
})();
console.log('DB ready:', db.ready);

// IIFE for one-time complex initialization
const SUPPORTED_FORMATS = (function() {
  const base = ['jpg', 'png', 'gif'];
  const modern = ['webp', 'avif'];
  return Object.freeze([...base, ...modern]);
})();
console.log('Formats:', SUPPORTED_FORMATS);

Block scoping with let/const and ES6 modules have replaced most IIFE use cases. However, IIFEs remain useful for async initialization and complex one-time computations that produce a constant value.

Function Composition

// Basic composition -- output of one function feeds into the next
const trim = str => str.trim();
const toLowerCase = str => str.toLowerCase();
const replaceSpaces = str => str.replace(/\s+/g, '-');

// Manual composition
const slug = replaceSpaces(toLowerCase(trim('  Hello World  ')));
console.log('Manual:', slug);

// Pipe utility (left to right)
function pipe(...fns) {
  return function(value) {
    return fns.reduce((acc, fn) => fn(acc), value);
  };
}

// Compose utility (right to left)
function compose(...fns) {
  return function(value) {
    return fns.reduceRight((acc, fn) => fn(acc), value);
  };
}

const createSlug = pipe(trim, toLowerCase, replaceSpaces);
console.log('Pipe:', createSlug('  Hello World  '));

const createSlug2 = compose(replaceSpaces, toLowerCase, trim);
console.log('Compose:', createSlug2('  Hello World  '));

// Composing data transformations
const double = n => n * 2;
const addTen = n => n + 10;
const square = n => n * n;

const transform = pipe(double, addTen, square);
console.log('Transform 3:', transform(3)); // ((3*2)+10)^2 = 256
console.log('Transform 5:', transform(5)); // ((5*2)+10)^2 = 400

Pipe applies functions left to right (the way you read). Compose applies right to left (matching mathematical notation). Both create reusable transformation pipelines from small, focused functions.

Key points

Concepts covered

IIFE, Module Pattern, Avoiding Global Pollution, Named vs Anonymous Functions, Function Composition