0%
Reading Settings
Font Size
18px
Line Height
1.5
Letter Spacing
0.01em
Font Family
Table of contents
    blog cover

    JavaScript Immediately Invoked Function Expression

    Interview
    Interview
    Frontend
    Frontend
    published 2025-07-17 14:50:21 +0700 · 4 mins read
    Today, we're diving into a common JavaScript pattern you've likely encountered: the Immediately Invoked Function Expression (IIFE). This pattern is used to manage scope and prevent global pollution.

    1. The Problem

    Consider this simple script:
    // language: html
    <script>
      const handleClick = () => console.log("Hello");
      handleClick(); // Output: Hello
    </script>

    Looks harmless, right? The handleClick function works as expected. Now, let's add another script block:
    // language: html
    <script>
      const handleClick = () => console.log("Hello");
      handleClick(); // Output: Hello
    </script>
    
    <script>
      handleClick(); // Output: Hello
    </script>

    As you can see, handleClick remains accessible in the subsequent script block. While const and let don't attach variables directly to the window object, they still exist in the shared global script environment for non-module scripts. This means const handleClick in a subsequent <script> tag would result in an error: Identifier 'handleClick' has already been declared

    The situation becomes more problematic with var declarations, which inherently attach to the window object, causing true global pollution:
    // language: html
    <script>
      var handleClick = () => console.log("Hello");
      handleClick(); // Output: Hello
    </script>
    
    <script>
      handleClick(); // Output: Hello
      window.handleClick(); // Output: Hello
    </script>

    Here, handleClick is not only visible across script blocks but also becomes an explicit property of the window object. This pollutes the global namespace, making your code susceptible to subtle, hard-to-debug issues if you use common function names that might conflict with other scripts, especially third-party libraries.

    So, how do we avoid this undesirable global exposure?

    2. Immediately Invoked Function Expression (IIFE)

    The core idea of an IIFE is to create a new function scope that encapsulates your variables and functions, preventing them from leaking into the global environment. Since var is function-scoped, and let/const are block-scoped (and thus also contained by a function scope), an IIFE effectively isolates your code.

    There are two primary syntaxes for IIFEs: using the traditional function keyword or the more modern arrow function. Both achieve the same scoping benefit:
    // language: javascript
    // Traditional Function Keyword IIFE
    (function () {
      var privateVar = "I'm private!";
      console.log(privateVar); // Output: I'm private!
    })();
    
    // Arrow Function IIFE (more concise)
    (() => {
      const anotherPrivateVar = "I'm also private, with an arrow!";
      console.log(anotherPrivateVar); // Output: I'm also private, with an arrow!
    })();
    
    // Outside the IIFEs, these variables are inaccessible
    console.log(typeof privateVar); // undefined
    console.log(typeof anotherPrivateVar); // undefined

    Now, let's go back to the previous example using an IIFE:
    // language: html
    <script>
    (() => {
      var handleClick = () => console.log("Hello from IIFE");
      handleClick(); // Output: Hello from IIFE
    })();
    </script>
    
    <script>
      handleClick(); // ReferenceError: handleClick is not defined
    </script>
    
    <script>
      window.handleClick(); // TypeError: window.handleClick is not a function
    </script>

    The handleClick variable is now exclusively contained within the IIFE's scope. It's not visible to subsequent script tags, nor is it attached to the window object. The IIFE itself is an expression that's immediately evaluated, and since we don't assign its result to any variable, it leaves no trace in the global scope.

    So why do we use IIFEs?
    • Global Scope Isolation: Prevents variable and function name conflicts by keeping them out of the global window object. This is crucial for integrating multiple scripts or third-party libraries.
    • Encapsulation: Creates a private execution context for a block of code, useful for one-time initialization or setup logic.
    • Module Pattern (Historical): Before native ES Modules, IIFEs were the primary mechanism for implementing modular patterns, allowing developers to expose only a public API while keeping internal details private via closures.

    3. ES Modules

    While IIFEs remain valuable, modern JavaScript offers a more robust and standardized solution for modularity and scope management: ECMAScript Modules (ES Modules), enabled by the type="module" attribute on your <script> tags.

    js/myUtils.js
    // language: javascript
    export function handleClick() {
      console.log("Hello from ES Module!");
    }

    index.html
    // language: html
    <script type="module">
      import { handleClick } from './js/myUtils.js';
      handleClick(); // Output: Hello from ES Module!
    </script>
    
    <script>
       handleClick() // ReferenceError: handleClick is not defined
    </script>

    When running this example, you'll observe that handleClick is only accessible within the module scope where it's imported. Today, ES Modules are the recommended standard for both browser and Node.js environments.

    4. Conclusion

    IIFEs establish private scope, preventing global namespace pollution (especially with var or in older code) by executing code once without exposing variables. For modern JavaScript, however, ES Modules offer a superior, standardized, and more maintainable approach to code organization and scope.
    While IIFEs have legacy and specific uses, ES Modules are the preferred choice for new development.

    Related blogs