JavaScript IIFE: Immediately Invoked Function Expressions
Master IIFEs in JavaScript. Learn about immediately invoked function expressions, their benefits, use cases, and modern alternatives.
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!