Start now →

Simulating Module Pattern and Dependency Injection Using IIFE in JavaScript

By Siddhi Satish Bhanushali · Published March 26, 2026 · 6 min read · Source: Level Up Coding
Ethereum
Simulating Module Pattern and Dependency Injection Using IIFE in JavaScript

Inspired by Hitesh Choudhary’s Chai aur Code YouTube channel

Yesterday I was watching a video by Chai aur Code (Hitesh Choudhary) and stumbled upon something that honestly blew my mind — how you can simulate the module pattern and dependency injection in JavaScript using something as simple as an IIFE. And here I was thinking these were only “backend framework” things. Turns out, JavaScript had this power all along, hiding in plain sight.

So let’s break this down with proper examples, the way I understood it.

First, What Even Is an IIFE?

IIFE stands for Immediately Invoked Function Expression. Think of it like ordering food at a drive-through. You place your order, you get your food, and you drive away. There’s no sitting down, no waiting for a table. The function runs the moment it’s defined and that’s it — job done.

(function () {
console.log("I ran immediately! No one called me.");
})();

The parentheses around the function make it an expression, and the () at the end invoke it right away. Simple as that.

The Problem IIFE Solves — Global Scope Pollution

Imagine you’re living in a hostel (I know, relatable for us students). There’s one shared fridge. Everyone dumps their stuff in it. Someone’s milk gets mixed up with someone else’s leftovers. Chaos.

That’s what happens when you dump all your variables into the global scope in JavaScript. Variable name collisions, unexpected overwrites, debugging nightmares.

// Without IIFE — everything is in the shared fridge (global scope)
var userName = "Siddhi";
var userAge = 24;
// Some other script also does this
var userName = "Random Library User";
// Oops. Your userName just got overwritten.

IIFE gives you your own mini-fridge. Your stuff stays private.

(function () {
var userName = "Siddhi";
var userAge = 24;
console.log(userName); // "Siddhi" — safe inside the IIFE
})();
console.log(userName); // ReferenceError! Can't access it from outside.

Now Let’s Build the Module Pattern

The module pattern takes this IIFE concept and levels it up. Think of it like a vending machine. You can see the buttons on the outside (public methods), but you have no idea what’s happening inside the machine (private variables). You press a button, you get a result. You can’t reach your hand inside and steal the gears.

const UserModule = (function () {
// Private — no one outside can touch these
let _name = "Siddhi";
let _loginCount = 0;
// Private function
function _incrementLogin() {
_loginCount++;
}
// Public API — the buttons on the vending machine
return {
getName: function () {
return _name;
},
login: function () {
_incrementLogin();
console.log(`${_name} logged in. Total logins: ${_loginCount}`);
},
getLoginCount: function () {
return _loginCount;
},
};
})();
UserModule.login(); // "Siddhi logged in. Total logins: 1"
UserModule.login(); // "Siddhi logged in. Total logins: 2"
console.log(UserModule.getName()); // "Siddhi"
console.log(UserModule.getLoginCount()); // 2
// Try accessing private stuff directly
console.log(UserModule._name); // undefined
console.log(UserModule._loginCount); // undefined

See what happened? The IIFE ran, created a closure, and returned only what we wanted to expose. Everything else stays locked inside. This is encapsulation — one of those fancy OOP concepts — achieved in JavaScript without a single classkeyword.

The Revealing Module Pattern — A Cleaner Cousin

There’s a slight variation called the Revealing Module Pattern where you define everything as private first and then “reveal” only what you want in the return statement. It makes the code a lot more readable.

const CartModule = (function () {
let _items = [];
function addItem(item) {
_items.push(item);
console.log(`${item} added to cart.`);
}
function getItems() {
return [..._items]; // return a copy, not the original
}
function getTotal() {
return _items.length;
}
// Reveal only what's needed
return {
addItem,
getItems,
getTotal,
};
})();
CartModule.addItem("Laptop");
CartModule.addItem("Mouse");
console.log(CartModule.getItems()); // ["Laptop", "Mouse"]
console.log(CartModule.getTotal()); // 2
console.log(CartModule._items); // undefined — still private!

Now the Fun Part — Dependency Injection with IIFE

Dependency Injection sounds intimidating, but it’s actually a very simple idea. Instead of a module going out and fetching its own dependencies, you hand them to it from outside.

Think of it like cooking. You could go to the store, buy your own ingredients, and cook. OR someone hands you all the ingredients and says “just cook.” The second approach is dependency injection. Your module doesn’t need to know where the ingredients came from. It just uses what it receives.

Without Dependency Injection (tightly coupled)

const NotificationService = (function () {
// This module goes and creates its own dependency
const logger = {
log: function (msg) {
console.log("[LOG]: " + msg);
},
};
function notify(message) {
logger.log(message);
console.log("Notification sent: " + message);
}
return { notify };
})();
NotificationService.notify("New order received!");

The problem? NotificationService is glued to that specific logger. You can't swap it out for testing. You can't replace it with a different logger. It's like hardcoding a restaurant into your GPS — what if it's closed?

With Dependency Injection (loosely coupled)

// Define the dependency separately
const ConsoleLogger = {
log: function (msg) {
console.log("[CONSOLE LOG]: " + msg);
},
};
const FileLogger = {
log: function (msg) {
console.log("[FILE LOG]: Writing to file — " + msg);
},
};
// Inject the dependency into the module
const NotificationService = (function (logger) {
function notify(message) {
logger.log(message);
console.log("Notification sent: " + message);
}
return { notify };
})(ConsoleLogger); // <-- Injecting ConsoleLogger here
NotificationService.notify("New order received!");
// [CONSOLE LOG]: New order received!
// Notification sent: New order received!

Now you can swap ConsoleLogger with FileLogger or even a mock logger during testing, and NotificationService doesn't care. It just uses whatever logger was handed to it. That's the beauty of DI.

A More Real-World Example — API Service with Injected Config

const AppConfig = {
baseUrl: "https://api.example.com",
timeout: 5000,
};
const MockConfig = {
baseUrl: "http://localhost:3000",
timeout: 1000,
};
const ApiService = (function (config) {
function fetchData(endpoint) {
console.log(`Fetching from ${config.baseUrl}/${endpoint}`);
console.log(`Timeout set to ${config.timeout}ms`);
// Imagine an actual fetch call here
}
function getBaseUrl() {
return config.baseUrl;
}
return { fetchData, getBaseUrl };
})(AppConfig); // Switch to MockConfig for testing!
ApiService.fetchData("users");
// Fetching from https://api.example.com/users
// Timeout set to 5000ms

During development, you pass MockConfig. In production, you pass AppConfig. The module stays the same. Zero changes inside.

Why Is This Underrated?

Here’s the thing — most tutorials jump straight to ES6 modules (import/export) and frameworks like Angular that have built-in DI containers. And that's great. But understanding the pattern behind the pattern is what separates a developer who uses tools from a developer who understands them.

The IIFE-based module pattern teaches you:

Even if you never write an IIFE in your day-to-day code, understanding this pattern makes you fundamentally better at JavaScript.

Wrapping Up

Huge shoutout to Hitesh Choudhary and the Chai aur Code channel for making these concepts click. The way Hitesh explains things with real-world context instead of dry theory is genuinely refreshing.

These patterns aren’t relics of the past — they’re the foundation that modern JavaScript tooling is built on. Understanding these concepts gives you superpowers when debugging, writing testable code, or just answering those tricky interview questions about closures and scope.

If you found this helpful, drop a follow and let’s connect! I write about JavaScript, web development, and the things I learn as an MS CS student navigating the tech world.

#JavaScript #IIFE #ModulePattern #DependencyInjection #WebDevelopment #ChaiAurCode #Programming


Simulating Module Pattern and Dependency Injection Using IIFE in JavaScript was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.

This article was originally published on Level Up Coding and is republished here under RSS syndication for informational purposes. All rights and intellectual property remain with the original author. If you are the author and wish to have this article removed, please contact us at [email protected].

NexaPay — Accept Card Payments, Receive Crypto

No KYC · Instant Settlement · Visa, Mastercard, Apple Pay, Google Pay

Get Started →