Advanced JavaScriptFeatured

JavaScript IIFE: Immediately Invoked Function Expressions

Master IIFEs in JavaScript. Learn about immediately invoked function expressions, their benefits, use cases, and modern alternatives.

By JavaScriptDoc Team
iifefunctionsscopemodulespatterns

JavaScript IIFE: Immediately Invoked Function Expressions

An Immediately Invoked Function Expression (IIFE) is a JavaScript function that runs as soon as it is defined. It's a design pattern that provides a way to execute functions immediately and create a private scope, preventing pollution of the global namespace.

What is an IIFE?

An IIFE is a function expression that is executed immediately after it's created. It consists of two parts: an anonymous function wrapped in parentheses, followed by another set of parentheses that invokes the function.

// Basic IIFE syntax
(function () {
  console.log('IIFE executed!');
})();

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

// IIFE with parameters
(function (name) {
  console.log(`Hello, ${name}!`);
})('World');

// IIFE that returns a value
const result = (function () {
  return 42;
})();
console.log(result); // 42

Why Use IIFEs?

1. Avoid Global Namespace Pollution

// Without IIFE - pollutes global scope
var counter = 0;
function incrementCounter() {
  counter++;
}

// With IIFE - variables are private
(function () {
  var counter = 0;
  function incrementCounter() {
    counter++;
  }
  // counter and incrementCounter are not accessible outside
})();

// Real-world example
(function () {
  const APP_VERSION = '1.0.0';
  const API_KEY = 'secret-key';

  function initializeApp() {
    console.log(`Initializing app v${APP_VERSION}`);
    // App initialization logic
  }

  initializeApp();
  // APP_VERSION, API_KEY, and initializeApp are private
})();

2. Create Private Variables and Methods

// Module pattern using IIFE
const Calculator = (function () {
  // Private variables
  let result = 0;
  const history = [];

  // Private methods
  function addToHistory(operation, value) {
    history.push({
      operation,
      value,
      result,
      timestamp: new Date(),
    });
  }

  // Public API
  return {
    add(value) {
      result += value;
      addToHistory('add', value);
      return this;
    },

    subtract(value) {
      result -= value;
      addToHistory('subtract', value);
      return this;
    },

    getResult() {
      return result;
    },

    getHistory() {
      // Return a copy to prevent external modification
      return [...history];
    },

    reset() {
      result = 0;
      history.length = 0;
      return this;
    },
  };
})();

// Usage
Calculator.add(10).subtract(3).add(5);
console.log(Calculator.getResult()); // 12
console.log(Calculator.history); // undefined (private)

3. Execute Initialization Code

// One-time setup code
(function () {
  // Check browser capabilities
  if (!window.localStorage) {
    console.warn('LocalStorage not supported');
    return;
  }

  // Initialize application
  const config = {
    theme: localStorage.getItem('theme') || 'light',
    language: localStorage.getItem('language') || 'en',
  };

  // Set up event listeners
  document.addEventListener('DOMContentLoaded', () => {
    applyTheme(config.theme);
    loadLanguage(config.language);
  });

  function applyTheme(theme) {
    document.body.className = `theme-${theme}`;
  }

  function loadLanguage(lang) {
    // Language loading logic
  }
})();

// Library initialization
(function (window, document) {
  'use strict';

  // Feature detection
  const features = {
    promises: typeof Promise !== 'undefined',
    fetch: typeof fetch !== 'undefined',
    intersectionObserver: typeof IntersectionObserver !== 'undefined',
  };

  // Polyfill loading
  if (!features.promises) {
    loadPolyfill('promise-polyfill.js');
  }

  function loadPolyfill(src) {
    const script = document.createElement('script');
    script.src = src;
    document.head.appendChild(script);
  }

  // Expose feature detection results
  window.__APP_FEATURES__ = features;
})(window, document);

IIFE Patterns and Variations

1. Classic IIFE Pattern

// Standard function expression
(function () {
  console.log('Classic IIFE');
})();

// Alternative syntax (less common)
(function () {
  console.log('Alternative IIFE');
})();

// Named IIFE (useful for debugging)
(function myIIFE() {
  console.log('Named IIFE');
  // myIIFE is only accessible inside the function
})();

// Unary operator IIFE
!(function () {
  console.log('Unary IIFE');
})();

+(function () {
  console.log('Another unary IIFE');
})();

// void operator IIFE
void (function () {
  console.log('Void IIFE');
})();

2. IIFE with Parameters

// Passing global objects
(function (global, $, undefined) {
  'use strict';

  // global is window
  // $ is jQuery
  // undefined is guaranteed to be undefined

  const APP = {
    version: '1.0.0',
    init() {
      console.log('App initialized');
    },
  };

  // Attach to global
  global.MyApp = APP;
})(window, jQuery);

// Dependency injection pattern
(function (modules) {
  // Module loader
  const loadedModules = {};

  function require(name) {
    if (!loadedModules[name]) {
      loadedModules[name] = modules[name]();
    }
    return loadedModules[name];
  }

  // Initialize app with dependencies
  const app = require('app');
  app.start();
})({
  app() {
    const utils = require('utils');
    return {
      start() {
        utils.log('App started');
      },
    };
  },

  utils() {
    return {
      log(message) {
        console.log(`[${new Date().toISOString()}] ${message}`);
      },
    };
  },
});

3. Revealing Module Pattern

// Complete module with private and public parts
const UserModule = (function () {
  // Private variables
  const users = new Map();
  let nextId = 1;

  // Private methods
  function validateEmail(email) {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
  }

  function validateUser(userData) {
    if (!userData.name || userData.name.trim().length === 0) {
      throw new Error('Name is required');
    }

    if (!validateEmail(userData.email)) {
      throw new Error('Invalid email format');
    }

    return true;
  }

  // Public API
  return {
    createUser(name, email) {
      const userData = { name, email };
      validateUser(userData);

      const id = nextId++;
      const user = { id, ...userData, createdAt: new Date() };
      users.set(id, user);

      return { id, name, email };
    },

    getUser(id) {
      const user = users.get(id);
      return user ? { ...user } : null;
    },

    updateUser(id, updates) {
      const user = users.get(id);
      if (!user) {
        throw new Error('User not found');
      }

      const updatedUser = { ...user, ...updates };
      validateUser(updatedUser);

      users.set(id, updatedUser);
      return { id, name: updatedUser.name, email: updatedUser.email };
    },

    deleteUser(id) {
      return users.delete(id);
    },

    getAllUsers() {
      return Array.from(users.values()).map((user) => ({
        id: user.id,
        name: user.name,
        email: user.email,
      }));
    },

    get userCount() {
      return users.size;
    },
  };
})();

// Usage
const user1 = UserModule.createUser('John Doe', 'john@example.com');
console.log(UserModule.getUser(user1.id));
console.log(UserModule.userCount); // 1

4. Singleton Pattern with IIFE

// Singleton instance
const Database = (function () {
  let instance;

  function createInstance() {
    // Private data
    const connections = new Set();
    let isConnected = false;

    // Private methods
    function log(message) {
      console.log(`[DB] ${new Date().toISOString()}: ${message}`);
    }

    // Public interface
    return {
      connect(connectionString) {
        if (isConnected) {
          log('Already connected');
          return Promise.resolve();
        }

        return new Promise((resolve) => {
          log(`Connecting to ${connectionString}`);
          setTimeout(() => {
            isConnected = true;
            log('Connected successfully');
            resolve();
          }, 1000);
        });
      },

      disconnect() {
        if (!isConnected) {
          log('Not connected');
          return;
        }

        isConnected = false;
        connections.clear();
        log('Disconnected');
      },

      query(sql) {
        if (!isConnected) {
          throw new Error('Database not connected');
        }

        log(`Executing query: ${sql}`);
        // Simulate query execution
        return Promise.resolve([
          { id: 1, name: 'Result 1' },
          { id: 2, name: 'Result 2' },
        ]);
      },

      get status() {
        return {
          connected: isConnected,
          activeConnections: connections.size,
        };
      },
    };
  }

  return {
    getInstance() {
      if (!instance) {
        instance = createInstance();
      }
      return instance;
    },
  };
})();

// Usage
const db1 = Database.getInstance();
const db2 = Database.getInstance();
console.log(db1 === db2); // true (same instance)

db1
  .connect('mongodb://localhost:27017/mydb')
  .then(() => {
    return db1.query('SELECT * FROM users');
  })
  .then((results) => {
    console.log(results);
  });

Modern Use Cases

1. Polyfills and Feature Detection

// Polyfill wrapped in IIFE
(function () {
  'use strict';

  // Check if Array.prototype.includes exists
  if (!Array.prototype.includes) {
    Array.prototype.includes = function (searchElement, fromIndex) {
      if (this == null) {
        throw new TypeError(
          'Array.prototype.includes called on null or undefined'
        );
      }

      const O = Object(this);
      const len = parseInt(O.length, 10) || 0;

      if (len === 0) {
        return false;
      }

      const n = parseInt(fromIndex, 10) || 0;
      let k = n >= 0 ? n : Math.max(len + n, 0);

      while (k < len) {
        if (searchElement === O[k]) {
          return true;
        }
        k++;
      }

      return false;
    };
  }
})();

// Feature detection and polyfill loading
(function () {
  const features = {
    fetch: 'fetch' in window,
    promises: 'Promise' in window,
    symbols: 'Symbol' in window,
    arrows: (function () {
      try {
        eval('() => {}');
        return true;
      } catch (e) {
        return false;
      }
    })(),
  };

  // Load polyfills based on missing features
  const polyfills = [];

  if (!features.fetch) {
    polyfills.push('https://cdn.polyfill.io/v3/polyfill.min.js?features=fetch');
  }

  if (!features.promises) {
    polyfills.push(
      'https://cdn.jsdelivr.net/npm/promise-polyfill@8/dist/polyfill.min.js'
    );
  }

  // Load all required polyfills
  polyfills.forEach((url) => {
    const script = document.createElement('script');
    script.src = url;
    script.async = false;
    document.head.appendChild(script);
  });

  // Expose feature support
  window.__FEATURES__ = features;
})();

2. Analytics and Tracking

// Analytics module with IIFE
(function (window, document) {
  'use strict';

  // Private state
  const events = [];
  const config = {
    endpoint: 'https://analytics.example.com/track',
    batchSize: 10,
    flushInterval: 5000,
  };

  let timer;

  // Private methods
  function sendBatch() {
    if (events.length === 0) return;

    const batch = events.splice(0, config.batchSize);

    fetch(config.endpoint, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        events: batch,
        timestamp: Date.now(),
        url: window.location.href,
        userAgent: navigator.userAgent,
      }),
    }).catch((error) => {
      console.error('Analytics error:', error);
      // Re-add events to queue on failure
      events.unshift(...batch);
    });
  }

  function startBatchTimer() {
    timer = setInterval(sendBatch, config.flushInterval);
  }

  function stopBatchTimer() {
    clearInterval(timer);
  }

  // Public API
  const Analytics = {
    track(eventName, properties = {}) {
      events.push({
        name: eventName,
        properties,
        timestamp: Date.now(),
      });

      if (events.length >= config.batchSize) {
        sendBatch();
      }
    },

    page(pageName, properties = {}) {
      this.track('page_view', {
        page: pageName,
        ...properties,
      });
    },

    identify(userId, traits = {}) {
      this.track('identify', {
        userId,
        traits,
      });
    },

    configure(options) {
      Object.assign(config, options);

      if (timer) {
        stopBatchTimer();
        startBatchTimer();
      }
    },
  };

  // Auto-track page views
  Analytics.page(document.title);

  // Start batch timer
  startBatchTimer();

  // Flush on page unload
  window.addEventListener('beforeunload', () => {
    stopBatchTimer();
    sendBatch();
  });

  // Expose global
  window.Analytics = Analytics;
})(window, document);

3. jQuery Plugin Pattern

// jQuery plugin using IIFE
(function ($, window, document, undefined) {
  'use strict';

  // Plugin name and defaults
  const pluginName = 'tooltip';
  const defaults = {
    position: 'top',
    delay: 200,
    animation: 'fade',
    theme: 'dark',
  };

  // Plugin constructor
  function Tooltip(element, options) {
    this.element = element;
    this.$element = $(element);
    this.options = $.extend({}, defaults, options);
    this._defaults = defaults;
    this._name = pluginName;

    this.init();
  }

  // Plugin prototype
  $.extend(Tooltip.prototype, {
    init() {
      this.createTooltip();
      this.bindEvents();
    },

    createTooltip() {
      const tooltipText =
        this.$element.attr('data-tooltip') ||
        this.options.text ||
        this.$element.attr('title');

      this.$tooltip = $('<div>', {
        class: `tooltip tooltip-${this.options.theme}`,
        text: tooltipText,
        css: {
          position: 'absolute',
          display: 'none',
          zIndex: 9999,
        },
      });

      $('body').append(this.$tooltip);
    },

    bindEvents() {
      const self = this;

      this.$element
        .on('mouseenter.' + pluginName, function () {
          self.show();
        })
        .on('mouseleave.' + pluginName, function () {
          self.hide();
        });
    },

    show() {
      clearTimeout(this.hideTimer);

      this.showTimer = setTimeout(() => {
        this.position();
        this.$tooltip.fadeIn(200);
      }, this.options.delay);
    },

    hide() {
      clearTimeout(this.showTimer);

      this.hideTimer = setTimeout(() => {
        this.$tooltip.fadeOut(200);
      }, 100);
    },

    position() {
      const pos = this.$element.offset();
      const width = this.$element.outerWidth();
      const height = this.$element.outerHeight();
      const tooltipWidth = this.$tooltip.outerWidth();
      const tooltipHeight = this.$tooltip.outerHeight();

      let top, left;

      switch (this.options.position) {
        case 'top':
          top = pos.top - tooltipHeight - 10;
          left = pos.left + (width - tooltipWidth) / 2;
          break;
        case 'bottom':
          top = pos.top + height + 10;
          left = pos.left + (width - tooltipWidth) / 2;
          break;
        case 'left':
          top = pos.top + (height - tooltipHeight) / 2;
          left = pos.left - tooltipWidth - 10;
          break;
        case 'right':
          top = pos.top + (height - tooltipHeight) / 2;
          left = pos.left + width + 10;
          break;
      }

      this.$tooltip.css({ top, left });
    },

    destroy() {
      this.$element.off('.' + pluginName);
      this.$tooltip.remove();
      this.$element.removeData('plugin_' + pluginName);
    },
  });

  // jQuery plugin definition
  $.fn[pluginName] = function (options) {
    return this.each(function () {
      if (!$.data(this, 'plugin_' + pluginName)) {
        $.data(this, 'plugin_' + pluginName, new Tooltip(this, options));
      }
    });
  };
})(jQuery, window, document);

// Usage
$('[data-tooltip]').tooltip({
  position: 'top',
  theme: 'light',
});

IIFE vs Modern Alternatives

ES6 Modules

// IIFE approach (old way)
const MyModule = (function () {
  const privateVar = 'private';

  return {
    publicMethod() {
      return privateVar;
    },
  };
})();

// ES6 Module approach (modern way)
// myModule.js
const privateVar = 'private';

export function publicMethod() {
  return privateVar;
}

// main.js
import { publicMethod } from './myModule.js';

Block Scope with let/const

// IIFE for scope isolation (old way)
(function () {
  var temp = 'temporary';
  // temp is not accessible outside
})();

// Block scope (modern way)
{
  let temp = 'temporary';
  const constant = 'constant';
  // temp and constant are not accessible outside
}

// Loop scope
// Old way with IIFE
for (var i = 0; i < 5; i++) {
  (function (index) {
    setTimeout(() => console.log(index), 100);
  })(i);
}

// Modern way with let
for (let i = 0; i < 5; i++) {
  setTimeout(() => console.log(i), 100);
}

Best Practices

1. Semicolon Usage

// Always use semicolon before IIFE to avoid issues
const value = 'something'(
  // Without semicolon - syntax error!
  function () {
    console.log('IIFE');
  }
)();

// With semicolon - correct
const value2 = 'something';

(function () {
  console.log('IIFE');
})();

// Or use alternative syntax
const value3 = 'something';

!(function () {
  console.log('IIFE');
})();

2. Parameter Documentation

/**
 * Application initialization module
 * @param {Window} global - Global window object
 * @param {Document} doc - Document object
 * @param {undefined} undefined - Ensures undefined is undefined
 */
(function (global, doc, undefined) {
  'use strict';

  // Module code here
})(window, document);

3. Error Handling

(function () {
  'use strict';

  try {
    // Initialization code
    initializeApp();
    setupEventListeners();
    loadConfiguration();
  } catch (error) {
    console.error('Application initialization failed:', error);

    // Fallback or error recovery
    showErrorMessage('Failed to initialize application');
  }

  function initializeApp() {
    // App initialization
  }

  function setupEventListeners() {
    // Event setup
  }

  function loadConfiguration() {
    // Config loading
  }

  function showErrorMessage(message) {
    // Error display
  }
})();

4. Testing Considerations

// Make IIFE testable by exposing in test environment
const MyModule = (function (isTest) {
  const private = {
    counter: 0,
    increment() {
      this.counter++;
    },
  };

  const public = {
    getCount() {
      return private.counter;
    },

    increase() {
      private.increment();
      return this.getCount();
    },
  };

  // Expose private methods for testing
  if (isTest) {
    public._private = private;
  }

  return public;
})(typeof process !== 'undefined' && process.env.NODE_ENV === 'test');

// In tests
if (MyModule._private) {
  // Test private methods
  MyModule._private.increment();
  console.assert(MyModule._private.counter === 1);
}

Conclusion

IIFEs are a fundamental JavaScript pattern that provides:

  • Scope isolation to avoid global namespace pollution
  • Private variables and methods through closure
  • One-time initialization code execution
  • Module pattern implementation

While modern JavaScript features like ES6 modules and block scope have reduced the need for IIFEs in some cases, they remain useful for:

  • Browser compatibility when modules aren't available
  • Quick scope isolation
  • Library and plugin development
  • Initialization code that needs to run immediately

Understanding IIFEs helps you:

  • Read and maintain legacy code
  • Create better encapsulation
  • Understand JavaScript's scoping rules
  • Build more modular applications

Whether you're maintaining older codebases or building modern applications, IIFEs remain a valuable tool in your JavaScript toolkit!