Chapter 4.4☕ 18 min read

IIFE, Module Pattern & Pre-ES6 Privacy

Function scope hi privacy tha — IIFE se andar band karo, bahar dikhne mat do.

0101 — IIFE: Immediately Invoked Function Expression

An IIFE (Immediately Invoked Function Expression) is a function that is defined and called in the same expression. It creates a new scope immediately — variables inside are private to that scope.

// Basic IIFE — two valid syntaxes
(function() {
  var private = 'only here';
  console.log('IIFE running!');
  console.log(private);
})();
// console.log(private); // ReferenceError — scope gone!

// Arrow IIFE
(() => {
  const secret = 42;
  console.log('Arrow IIFE:', secret);
})();

// IIFE with return value
const result = (function() {
  const x = 10;
  const y = 20;
  return x + y;
})();
console.log(result); // 30 — IIFE is done, but value escaped

// IIFE with parameters
(function(name, greeting) {
  console.log(greeting + ', ' + name + '!');
})('Sai', 'Namaste');

// Why outer parens are needed:
// function() { }(); // SyntaxError — declaration cannot be invoked
// (function() { })(); // OK — expression, not declaration
// !function() { }();  // also OK — unary operator forces expression context
// +function() { }();  // also OK — unary + forces expression
In V8's parser, seeing 'function' as the very first token of a statement means 'function declaration' — which cannot be immediately invoked. Wrapping in () changes the parse context to expression mode. The outer () are not calling the function — they are changing HOW the parser reads the function keyword. The second () at the end actually calls it.
0202 — The Global Scope Problem IIFE Solved

Before ES6 modules: every <script> tag shared the same global scope. Multiple library scripts — all variables on window — name collisions!

Classic collision: jQuery used $, Prototype.js also used $ — war!

jQuery's fix: wrapped the entire library in an IIFE, exposing only ONE name to global. Everything else was private inside the closure.

// The global collision problem (pre-ES6)
// Script 1 loads:
var utils = 'script1 utils';  // on window!
// Script 2 loads:
var utils = 'script2 utils';  // OVERWRITES script1!
// Script 1's utils is gone — silent bug

// IIFE solution: only expose what you need
var MyLib = (function() {
  var version = '1.0.0';
  var privateCache = {};

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

  return {
    get: function(key) { return privateHelper(key); },
    set: function(key, val) { privateCache[key] = val; },
    getVersion: function() { return version; }
  };
})();

MyLib.set('user', { name: 'Sai' });
console.log(MyLib.get('user'));        // { name: 'Sai' }
console.log(MyLib.getVersion());       // '1.0.0'
// MyLib.privateCache                  // undefined — private!
// MyLib.privateHelper                 // undefined — private!

// jQuery used this EXACT pattern:
// var jQuery = (function(window, undefined) {
//   var jQuery = function(selector) { ... };
//   return jQuery;
// })(window);
📋 jQuery passing 'window' and 'undefined' as IIFE parameters was intentional.
It made window a local variable (faster lookup in scope chain) and guaranteed that 'undefined' was actually undefined — in old browsers, undefined could be reassigned! This is the level of paranoia required in pre-strict-mode JS.
0303 — The Module Pattern: Private State with Public API

The Module Pattern is an IIFE that returns an object — the public interface. Private variables and functions live inside the IIFE closure. Public methods are properties of the returned object.

// Full module pattern — counter with private state
var Counter = (function() {
  // Private state
  var count = 0;
  var step = 1;
  var history = [];

  // Private function
  function logHistory(action) {
    history.push({ action: action, value: count, time: Date.now() });
  }

  // Public API
  return {
    increment: function() {
      count += step;
      logHistory('increment');
      return count;
    },
    decrement: function() {
      count -= step;
      logHistory('decrement');
      return count;
    },
    setStep: function(n) { step = n; },
    reset: function() {
      count = 0;
      logHistory('reset');
      return count;
    },
    getHistory: function() { return history.slice(); },
    value: function() { return count; }
  };
})();

Counter.increment(); // 1
Counter.increment(); // 2
Counter.setStep(5);
Counter.increment(); // 7
console.log(Counter.value());      // 7
console.log(Counter.getHistory()); // [{...}, ...]
// Counter.count    — undefined (private)
// Counter.history  — undefined (private)

// Module augmentation — add features from another file
var Counter = (function(module) {
  module.double = function() {
    return module.increment() + module.increment();
  };
  return module;
})(Counter || {});
The module pattern creates one closure per module instantiation. All public methods share the SAME Lexical Environment (the IIFE's scope). This means they all have access to the same private variables — which is what makes the pattern work. The returned object's methods are not copies of the functions — they are references to the same functions that close over the private scope.
0404 — IIFE vs Modern Alternatives

ES6 modules made IIFEs largely unnecessary for scope isolation. Each file has its own scope automatically. Block scope with let/const replaces simple IIFEs. Class private fields (#) replace IIFE for per-instance private data.

// Before ES6 — IIFE for privacy:
var module = (function() {
  var private = 'secret';
  return { get: function() { return private; } };
})();

// ES6 module (file has its own scope automatically):
// module.js — no IIFE needed!
const private = 'secret'; // not on window
export const get = () => private;

// Block scope replaces simple IIFE for one-off isolation:
// Old way:
(function() {
  var temp = expensiveComputation();
  doSomethingWith(temp);
})();

// New way — block scope:
{
  const temp = expensiveComputation();
  doSomethingWith(temp);
}

// Async IIFE — still very useful in 2024:
(async () => {
  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    console.log(data);
  } catch (err) {
    console.error('Failed:', err);
  }
})();

// Class private fields (ES2022):
class BankAccount {
  #balance = 0; // truly private
  deposit(n) { this.#balance += n; }
  get balance() { return this.#balance; }
}
When IIFE is still useful in 2024: initialization code that runs once, async top-level in non-module scripts (where top-level await is not available), avoiding global pollution in third-party scripts. The async IIFE pattern is extremely common in Node.js scripts and browser code that can't use ES modules.
0505 — Real IIFE Patterns in Legacy Code

Legacy codebases are full of IIFE patterns. Here are the ones you'll encounter most often:

// 1. jQuery plugin — safe $ alias
(function($) {
  $.fn.highlight = function(color) {
    return this.css('background-color', color || 'yellow');
  };
})(jQuery);

// 2. UMD pattern — works everywhere
(function(root, factory) {
  if (typeof define === 'function' && define.amd) {
    define(['exports'], factory);     // AMD (RequireJS)
  } else if (typeof module !== 'undefined' && module.exports) {
    factory(module.exports);          // CommonJS (Node.js)
  } else {
    factory(root.MyLib = {});         // Global browser
  }
})(this, function(exports) {
  exports.version = '1.0.0';
  exports.greet = function(name) { return 'Hello, ' + name; };
});

// 3. Init-time branching — decide ONCE, not every call
var addEvent = (function() {
  if (window.addEventListener) {
    return function(el, type, fn) {
      el.addEventListener(type, fn, false);
    };
  } else {
    return function(el, type, fn) {
      el.attachEvent('on' + type, fn); // IE8 fallback
    };
  }
})();

// 4. Namespace pattern with IIFE
var App = App || {};
App.utils = (function() {
  var version = '2.0';
  return {
    getVersion: function() { return version; },
    formatDate: function(d) { return d.toISOString().slice(0, 10); }
  };
})();
📋 The UMD pattern is still found in npm packages that support both browser globals and Node.js require(). When you see a library that works with both script tags and require(), it's almost certainly using UMD. Modern packages use ES modules (type: module in package.json) but many older packages still ship UMD bundles for compatibility.

Lo kar liya — Key Points:

  • ✅ IIFE = function defined and immediately called — creates a private scope that dies instantly but can return values
  • ✅ The outer () change the function from a declaration to an expression — required for immediate invocation
  • ✅ Module pattern = IIFE returning an object — private state inside closure, public API on the returned object
  • ✅ Before ES6, IIFE was the ONLY way to create private scope — no block scope, no modules, no class private fields
  • ✅ ES6 module files have their own scope automatically — no IIFE needed for isolation in modern code
  • ✅ Async IIFE (async () => await ... )() is still useful in 2024 when top-level await is unavailable
Course Search
Search across all chapters & stages
📖

Search the course

Type any topic — branching, stash, rebase, hooks — and jump straight to that chapter.

merge branchesgit stashundo commitrebase