7 Modern Use Cases of IIFE
YCM Jason

YCM Jason @ycmjason

About: I cook delicious TypeScript / JavaScript nuggets. 🍳

Location:
London, UK
Joined:
Sep 30, 2017

7 Modern Use Cases of IIFE

Publish Date: May 23
15 8

In the last article, I introduced the try-catch IIFE pattern. Some of you found it clever, while others couldn’t stand the syntax. And honestly? Fair. IIFE (Immediately Invoked Function Expression) can look odd at first glance.

But I’m here to change your mind.

IIFE isn’t just a relic of the var era, there are still many modern use cases.

Here are 7 modern use cases of IIFE that go beyond legacy code and show how they can improve clarity, encapsulation, and expressiveness in your everyday code.

⏳ What Is an IIFE? (And Why Was It Essential in the var Era?)

Before we look at modern use cases, let’s rewind a bit.

IIFE stands for Immediately Invoked Function Expression. It's a function that runs right after it’s defined. Like this:

(() => {
  console.log("Hello!");
})();
Enter fullscreen mode Exit fullscreen mode

You define a function and immediately invoke it. But why go through this strange ritual?

Well, back in the var era, var was the only way to define variables. It is function-scoped, unlike const and let which are block-scoped. So, variables declared inside for loops, if / switch statements weren’t isolated — they leaked into the surrounding scope.

Here’s an infamous example:

var buttons = [];

for (var i = 0; i < 3; i++) {
  buttons[i] = function () {
    console.log(i);
  };
}

buttons[0](); // 3
buttons[1](); // 3
buttons[2](); // 3
Enter fullscreen mode Exit fullscreen mode

Surprise! They all log 3. That’s because var i was shared across all iterations!

To fix this, devs discovered a clever trick: wrap each loop iteration in an IIFE:

for (var i = 0; i < 3; i++) {
  (function (lockedInIndex) {
    buttons[lockedInIndex] = function () {
      console.log(lockedInIndex);
    };
  })(i);
}
Enter fullscreen mode Exit fullscreen mode

Now it works as expected:

buttons[0](); // 0
buttons[1](); // 1
buttons[2](); // 2
Enter fullscreen mode Exit fullscreen mode

This worked because each IIFE created a new function scope, capturing the correct value of i in each iteration.

Ben Alman wrote a seminal article on this pattern back in 2010 — it was so important that he helped define the term IIFE and documented all the ways to write one correctly. Check out his article here.

He even clarified the distinction between function declarations and expressions, which tripped up countless developers at the time. The () around the function weren’t for nothing! They were crucial so that the function is treated as an expression, allowing it to be executed immediately.

In short:

  • Without let and const, IIFE was the only way to get around problems with function scoping.
  • It became a powerful tool for encapsulation, module patterns, closures, and safe variable use.

Nowadays, we have proper block scoping via let and const. So IIFE has started to fade out from our memories.

...but that doesn’t mean it’s useless. As you’ll see below, IIFE still unlocks structure, encapsulation, and readability in surprisingly modern contexts.

Let’s dive in.

Use Case 1: Enable async/await

Ever tried using await in a place where you're not allowed to write async directly, like inside the useEffect hook in React?

If you've ever tried to do something like this:

useEffect(async () => {
  const response = await MyAPI.getData(someId);
  // ...
});
Enter fullscreen mode Exit fullscreen mode

You are probably no stranger to this error message:

React's Error Message

The recommended way to fix this is to define an async function in your effect and call it immediate. And guess what a function that is called immediately is? An IIFE!

The example code in the error message recommends you to do:

useEffect(() => {
  const fetchData = async () => {
    const response = await MyAPI.getData(someId);
    // ...
  }
  fetchData();
});
Enter fullscreen mode Exit fullscreen mode

But why bother naming the function that is only used once? Naming is well-known to be one of the hardest thing in programming, if not the hardest. So do yourself a favour, skip the naming and just use IIFE:

useEffect(() => {
  (async () => {
    const response = await MyAPI.getData(someId);
    // ...
  })();
});
Enter fullscreen mode Exit fullscreen mode

Use Case 2: const with switch

Sometimes switch statements are used just to initialise a variable.

Let's check out this real example from React:

let postTaskPriority;
switch (priorityLevel) {
  case ImmediatePriority:
  case UserBlockingPriority:
    postTaskPriority = 'user-blocking';
    break;
  case LowPriority:
  case NormalPriority:
    postTaskPriority = 'user-visible';
    break;
  case IdlePriority:
    postTaskPriority = 'background';
    break;
  default:
    postTaskPriority = 'user-visible';
    break;
}

// `postTaskPriority` is never reassigned again, but it is a `let`! 😢
Enter fullscreen mode Exit fullscreen mode

The variable is never reassigned, yet we’re forced to use let because the value is assigned inside the switch.

With IIFE:

const postTaskPriority = (() => {
  switch (priorityLevel) {
    case ImmediatePriority:
    case UserBlockingPriority:
      return 'user-blocking';
    case LowPriority:
    case NormalPriority:
      return 'user-visible';
    case IdlePriority:
      return 'background';
    default:
      return 'user-visible';
  }
})();

// `postTaskPriority` can NEVER be reassigned again, because it is a `const`! 🎉
Enter fullscreen mode Exit fullscreen mode

No let. No risk of accidental reassignment. And we even skip all the break statements! HUGE WIN!

You can also apply the same pattern to if statements. Here’s a real-world example from Angular:

let kind: DisplayInfoKind;
if (symbol.kind === SymbolKind.Reference) {
  kind = DisplayInfoKind.REFERENCE;
} else if (symbol.kind === SymbolKind.Variable) {
  kind = DisplayInfoKind.VARIABLE;
} else if (symbol.kind === SymbolKind.LetDeclaration) {
  kind = DisplayInfoKind.LET;
} else {
  throw new Error(
    `AssertionError: unexpected symbol kind ${SymbolKind[(symbol as Symbol).kind]}`,
  );
}
Enter fullscreen mode Exit fullscreen mode

With IIFE:

const kind = (() => {
  if (symbol.kind === SymbolKind.Reference) {
    return DisplayInfoKind.REFERENCE;
  }

  if (symbol.kind === SymbolKind.Variable) {
    return DisplayInfoKind.VARIABLE;
  }

  if (symbol.kind === SymbolKind.LetDeclaration) {
    return DisplayInfoKind.LET;
  }

  throw new Error(
    `AssertionError: unexpected symbol kind ${SymbolKind[(symbol as Symbol).kind]}`,
  );
})();
Enter fullscreen mode Exit fullscreen mode

Clean, top-down logic. Guard clauses instead of nested else-ifs. And as a bonus, no need explicitly type kind as TypeScript infers the type for free!

Use Case 3: Inline Guard Clauses

Ever had to chain multiple ternary operators in your code?

Here's a code snippet from Vue.js:

const defer =
  typeof process !== 'undefined' && process.nextTick
    ? process.nextTick
    : typeof Promise !== 'undefined'
    ? fn => Promise.resolve().then(fn)
    : typeof setTimeout !== 'undefined'
    ? setTimeout
    : noop
Enter fullscreen mode Exit fullscreen mode

Multiple chain of ternary operators can become unreadable very quickly. With IIFE:

const defer = (() => {
  if (typeof process !== 'undefined' && process.nextTick) {
    return process.nextTick;
  }

  if (typeof Promise !== 'undefined') {
    return fn => Promise.resolve().then(fn);
  }

  if (typeof setTimeout !== 'undefined') {
    return setTimeout;
  }

  return noop;
})();
Enter fullscreen mode Exit fullscreen mode

It is so much cleaner, right?

Each branch is clear, readable, and obvious. You can instantly see there are three conditions and a default fallback to noop.

Use Case 4: Stateful Functions

Let’s say you need a function that tracks its own state:

let nextCount = 0;
const getNextCount = () => nextCount++;
Enter fullscreen mode Exit fullscreen mode

But nextCount is exposed to the whole file now! Anyone can modify it and it comes down to trusting developers and your future self not to mess with it!

Now try:

const getNextCount = (() => {
  let nextCount = 0;
  return () => nextCount++;
})();
Enter fullscreen mode Exit fullscreen mode

Fully encapsulated. No accidental modifications.

Use Case 5: Object Property Initialisation

IIFE can be used to create a more encapsulated object construction pattern.

I came across this piece of code from Angular:

export async function generateMetadata(
  path: string,
  config: TutorialConfig,
  files: FileAndContentRecord,
): Promise<TutorialMetadata> {
  const tutorialFiles: FileAndContentRecord = {};
  const {dependencies, devDependencies} = JSON.parse(
    files['package.json'] as string,
  ) as PackageJson;

  config.openFiles?.forEach((file) => (tutorialFiles[file] = files[file]));

  return {
    type: config.type,
    openFiles: config.openFiles || [],
    allFiles: Object.keys(files),
    tutorialFiles,
    answerFiles: await getAnswerFiles(path, config, files),
    hiddenFiles: config.openFiles
      ? Object.keys(files).filter((filename) => !config.openFiles!.includes(filename))
      : [],
    dependencies: {
      ...dependencies,
      ...devDependencies,
    },
  };
}
Enter fullscreen mode Exit fullscreen mode

Pretty standard pattern. We do some setup — in this case, computing tutorialFiles and parsing dependencies — and then use them in the returned object.

But the setup code can get mixed up really easily and the setup and the usage of the setup are in 2 completely different places!

With IIFE, we can keep each property's setup and usage within its own function:

export async function generateMetadata(
  path: string,
  config: TutorialConfig,
  files: FileAndContentRecord,
): Promise<TutorialMetadata> {
  return {
    type: config.type,
    openFiles: config.openFiles || [],
    allFiles: Object.keys(files),
    tutorialFiles: (() => {
      const tutorialFiles: FileAndContentRecord = {};
      config.openFiles?.forEach((file) => (tutorialFiles[file] = files[file]));
      return tutorialFiles;
    })(),
    answerFiles: await getAnswerFiles(path, config, files),
    hiddenFiles: config.openFiles
      ? Object.keys(files).filter(
          (filename) => !config.openFiles!.includes(filename),
        )
      : [],
    dependencies: (() => {
      const { dependencies, devDependencies } = JSON.parse(
        files["package.json"] as string,
      ) as PackageJson;

      return {
        ...dependencies,
        ...devDependencies,
      };
    })(),
  };
}
Enter fullscreen mode Exit fullscreen mode

Use Case 6: Try-Catch IIFE

Go check out my previous article to learn more.

But basically:

const user = (() => {
  try {
    return await getUser();
  } catch (err) {
    // handle err
    return null;
  }
})();
Enter fullscreen mode Exit fullscreen mode

Use Case 7: Hierarchies of Abstraction

Sometimes our code can look very flat even when semantically there should be some sort of hierarchies:

const getPosition = (...) => {
  const a = ...
  const b = ...
  const x = getX(a, b);

  const c = ...
  const d = ...
  const y = getY(c, d);

  return [x, y];
};
Enter fullscreen mode Exit fullscreen mode

In this example, the hierarchy look something like this:

hierarchy diagram

With IIFE, we can visually represent such hierarchy:

const getPosition = (...) => {
  const x = (() => {
    const a = ...
    const b = ...
    return getX(a, b);
  })();

  const y = (() => {
    const c = ...
    const d = ...
    return getY(c, d);
  })();

  return [x, y]
};
Enter fullscreen mode Exit fullscreen mode

It is clear, encapsulated, and easy to zoom in and out of the logic without friction.

Final Thoughts

Despite being a pattern born in the early days of JavaScript, IIFE still earns its place in modern development. It’s no longer something we have to use — and that’s precisely what makes it powerful.

When you choose to use IIFE today, you’re doing it for structure, clarity, and intent. You’re declaring, “this bit of logic is self-contained and intentional.” Whether it's to enable async/await, protect internal state, or express a hierarchy of abstraction, IIFE gives you an extra layer of control over your code.

Sure, it can be noisy — but in the right places, it can be beautiful.

So the next time you reach for a let just because switch says so… maybe, just maybe, give IIFE another look.

Let me know what you think — I’d love to hear your thoughts!

Comments 8 total

  • Dotallio
    DotallioMay 24, 2025

    I use that async IIFE in React effects all the time, makes things so much cleaner. Ever hit any quirks using IIFE with TypeScript types?

    • YCM Jason
      YCM JasonMay 24, 2025

      Yeah, async IIFEs really come in handy!

      As for TypeScript — not really any issues there! If anything, as some examples show (like using const with switch), IIFEs can actually help us type things better. Since an IIFE is just a function that runs immediately, TypeScript understands it very well — often more cleanly than if we tried to work around the same logic in other ways.

  • Nevo David
    Nevo DavidMay 24, 2025

    This is solid stuff - been cool seeing little tricks like IIFE stick around. What actually makes those habits catch on long-term? Just practice, or folks finally get tired of messy code?

    • YCM Jason
      YCM JasonMay 24, 2025

      Totally! I think one thing that helps these habits stick is developing a kind of unwillingness to compromise — like, once you’ve seen how clean something like "const with switch" can be with an IIFE, it’s hard to go back to a messier alternative.

      Over time, you just start reaching for these patterns naturally because they feel right. Practice helps, but it’s also about caring enough to keep your code tidy when it’s easy not to.

  • Nathan Tarbert
    Nathan TarbertMay 24, 2025

    insane level of detail in this, i always end up going back to iife for random reasons so seeing all these examples feels real - you think using little habits like this over time matters more than just following current trends?

    • YCM Jason
      YCM JasonMay 24, 2025

      Thanks! Really glad the examples were useful — that was exactly what I was hoping for.

      I don’t think IIFEs really go against any trends these days. If anything, they quietly solve problems that often get overlooked. Take the "const with switch" pattern — some folks might default to let without realising there’s a cleaner option. IIFEs just offer a nice little trick that still holds up, even if it’s not always front of mind.

  • YCM Jason
    YCM JasonMay 24, 2025

    Thank you for dropping by!

Add comment