In the (not that distant) past the web frontend tooling ecosystem wouldn't offer any code splitting standards known and used today, such as modules. Those times were long before bundlers and loaders, of which the inception of RequireJS was the first real breakthrough back in 2010. At that time, it was crucial for web engineers to remember to encapsulate their JavaScript code by hand to reduce interference with the global object to a minimum and avoid possible namespace collisions.
Without a doubt, the first thing the web engineers would turn to was to create a closure over their code within the lexical scope of an IIFE – an acronym for immediately invoked function expression. Those were the golden times of IIFE. It was used extensively on the web and served as a worthy predecessor to modern modules. Back then, it was a standard practice and universal truth.
Now, thanks to many inventive minds, the rise of computing power, and years of general technology advancement, web frontend engineers are not bound to use IIFEs anymore, but let’s pause for a moment and try to answer the question – has IIFE rightfully become a relic of the past?
Why use IIFE?
Let’s start with what an IIFE actually looks like. Here’s an example of an IIFE versus a standard named function:
(function iife() { return 'example'; })(); // >>> 'example'
function namedFunction() { return 'example'; } // >>> undefined
namedFunction(); // >>> 'example'
Like any function in JavaScript, IIFE can be defined with function keyword or arrow function expression. It is a callable object which encapsulates its content within a lexical scope, etc. In short – all the things that apply to JavaScript functions are true for IIFEs as well. What differentiates an IIFE from a standard function is that, just as its name states, it is invoked immediately. It won’t be assigned anywhere.
What may (and most likely will) be assigned, however, is its return value, unless it’s a side effect function. There are two major gains with this pattern. For one – if a pure function, it does not store anything in the host machine’s memory, saving up space. Secondly, and most importantly – if used properly, the code utilizing IIFE can be a lot more readable and better structured, often reflecting the author's intention better than any other pattern or expression.
And don’t just take my word for it, examples lie up ahead.
Closures
IIFE is, most of all, used for closures. In fact, all examples given in this article are centered on closures (and also the fact that functions return a single value). If you’re unfamiliar with closures, they encapsulate constants and variables, making them available only within the function’s inner lexical scope, and inaccessible from the outer scope, outside of the given function.
The web is full of closure examples, but let’s take a closer look at a closure made with an IIFE:
const { getValue, incrementValue } = (function makeClosure() {
const persistedState = { value: 0 };
return {
getValue() {
return persistedState.value;
},
incrementValue() {
return (persistedState.value += 1);
},
};
})();
getValue(); // >>> 0
incrementValue(); // >>> 1
getValue(); // >>> 1
The function is namedmakeClosure here only to reflect its purpose. Because the IIFE pattern enforces placing parentheses around the function definition, with this syntax the function itself is not stored in the memory (its lexical context is!) and even though named, it is not accessible by its name. In fact, most of the time IIFEs can get by as unnamed, anonymous functions. Back to the example though – there’s a closure over persistedState object which allows, no more and no less than, getting its value property and incrementing it.
No other operation from outside of the makeClosure function can be carried out on the persistedState object and its value property. Of course, makeClosure could be a standard named function defined and called in two separate expressions respectively, but the scenario here is that it’s called only once, in this particular place in space and time. This is where an IIFE shines – it makes the author's intention very clear. A single use case with no possibility to re-do the exact same thing anywhere else. That is, unless of course the code is copied and pasted, but let’s keep the DRY (Don’t Repeat Yourself) principle in mind.
Mutations and variable reassignment
Software engineering is an area filled with source code object mutations and complex conditions, often combined with variable reassignment and JavaScript is no exception to that. Although most of the time JavaScript engineers are doing their best to conform with the functional programming paradigm and follow, among others, immutability rules, browsers are stateful runtime environments and don’t make the job easy.
Every so often when reading source code, we come across some kind of variation of the following:
function doSomething(input) {
let inputLabel;
if (input.isA()) {
inputLabel = 'a';
} else if (input.isB()) {
inputLabel = 'b';
} else {
inputLabel = 'c';
}
return `input is ${inputLabel}`;
}
Even worse, sometimes output cannot be finally assigned in a single condition block but is also reassigned at some point later. That’s the proper place to use an IIFE and utilize the fact that functions always return a single value. Let’s take a look at how to make what’s happening in the source code here clearer:
function doSomething(input) {
const inputLabel = (function () {
if (input.isA()) {
return 'a';
}
if (input.isB()) {
return 'b';
}
return 'unknown';
})();
return `input is ${inputLabel}`;
}
The code is made immutable and not only does it feel easier to read but safer to maintain in the future as well – inputLabel is a constant value returned from a function rather than being assigned (and possibly reassigned later) at any random point due to the fact that it was declared with the let keyword before. Notice how this flows:
changes into this:
Other common scenarios may include using switch statements, forand while loops, etc. In fact, some engineers may even prefer the switch (true) { ... }pattern to be used in this very scenario! Again, IIFE is incredibly elegant when used in conjunction with switch statements, as opposed to variable reassignment. Consider this:
function doSomething(input) {
let inputLabel;
switch (true) {
case input.isA():
inputLabel = 'a';
break;
case input.isB():
inputLabel = 'b';
break;
default:
inputLabel = 'unknown';
break;
}
return `input is ${inputLabel}`;
}
versus:
function doSomething(input) {
const inputLabel = (function () {
switch (true) {
case input.isA():
return 'a';
case input.isB():
return 'b';
default:
return 'unknown';
}
})();
return `input is ${inputLabel}`;
}
These are just basic examples but they shed sufficient light on what can be achieved with IIFEs in modern code bases. Looking up existing parts of the code which could benefit from this pattern is a great place to start. With some practice and mindfulness, after a while it becomes evident how the code style can be improved.
When not to use IIFE
Hopefully, it’s now clear what the advantages of using an IIFE are and the most beneficial of its use case scenarios. To do the topic justice, let’s briefly touch upon the disadvantages as well. IIFE is a very specific pattern suitable for very specific needs and should definitely not replace every possible function call and definition. Once again, IIFE is best used in cases when a function is called only in a single place in the entire source code. Not being able to assign an IIFE makes it a bad choice to be called from multiple places, because it would require the function to be copied and pasted each time. The same fact also makes unit tests impossible to run on IIFE, so if there is unit test execution planned on a function, it cannot be immediately invoked – such functions always have to be assigned, stored and exported.
Conclusion
There’s no denying that nowadays IIFE is still a valuable pattern. Its value strongly depends on the code style adopted in specific projects and it's admittedly not suitable for everyone everywhere. There are many projects, however, which adopt an immutable code style but at the same time make space for exemptions like let variable reassignments. There definitely are areas where IIFE shines and they have been highlighted above. Whether people like it or not, without a doubt IIFE still deserves recognition, making even some modern code more efficient and readable.