Sitemap
Usama Saleem

Usama Saleem

I'm a senior software engineer with over 3 years of experience in various technologies including TypeScript, React, Node.js, Express, MongoDB, and AWS

Understanding Singleton Behavior in JavaScript Using Closures

2 min readJan 27, 2024

--

Press enter or click to view image in full size

Introduction

JavaScript’s closures provide a powerful mechanism for encapsulating state and behavior. In this article, we’ll explore how to leverage closures to achieve a singleton pattern in JavaScript. By creating a single instance that can be shared across different files or modules, we can ensure consistent state and behavior throughout our application.

What is a Singleton?

A singleton is a design pattern that restricts the instantiation of a class to a single instance and provides a global point of access to that instance. It’s particularly useful when there should be exactly one instance of a class, and it needs to coordinate actions across an application.

Using Closures for Singleton Behavior

JavaScript closures allow functions to retain access to variables from their outer scope, even after the outer function has finished executing. This property makes closures an excellent choice for creating singletons.

Creating a Closure with Shared State

Let’s consider a simple example with a counter object:

Get Usama Saleem’s stories in your inbox

Join Medium for free to get updates from this writer.

// counterFunctions.ts
export function outer() {
let counter = {
name: 'name is here',
count: 0,
};

function incrementCounter(name) {
counter.count++;
counter.name = name;
}

return {
getCounter: () => counter,
incrementCounter: incrementCounter,
};
}

Achieving Singleton Behavior

To guarantee that the singleton pattern is enforced, we establish a distinct module responsible for overseeing the singleton instance. This precaution is necessary because importing the outer function directly would result in the loss of the closed-over variable environment (COVE).

// counterSingleton.js
import { outer } from './counterFunctions';

const singletonInstance = outer();

export default singletonInstance;

Now, we can import counterSingleton.js in various files and use the same singleton instance across the application.

Example: Using the Singleton Instance

Let’s see how we can use the singleton instance in different files with console output:

// file1.ts
import counterSingleton from './counterSingleton';

console.log('Initial Counter:', counterSingleton.getCounter()); // {name: 'name is here',count: 0}

counterSingleton.incrementCounter('Alice');
console.log('Counter after increment:', counterSingleton.getCounter()); // {name: 'name is here',count: 1}
// file2.js
import counterSingleton from './counterSingleton';

console.log('Initial Counter in file2:', counterSingleton.getCounter()); // {name: 'name is here',count: 1}

counterSingleton.incrementCounter('Bob');
console.log('Counter after increment in file2:', counterSingleton.getCounter()); // {name: 'Bob is here',count: 2}

Conclusion

By utilizing closures in JavaScript, we can implement a robust and efficient singleton pattern. This approach facilitates a clean separation of concerns, ensures a consistent state, and enhances the overall maintainability of our codebase. Singleton behavior is a valuable design pattern that can be particularly beneficial in larger applications where coordination and data consistency are crucial.

--

--

Usama Saleem
Usama Saleem

I'm a senior software engineer with over 3 years of experience in various technologies including TypeScript, React, Node.js, Express, MongoDB, and AWS