Unlock the Hidden Power of the Singleton Design Pattern

Do you have problems with code that makes too many duplicate objects? You’re not alone. This guide explains the Singleton pattern in TypeScript. It’s a useful design pattern that many coders misunderstand. I’ll show you how it works in simple terms that beginners can follow.
Singletons Explained Simply
Think of a house with just one kitchen. You can enter through any door, but you’ll always find the same kitchen. A Singleton works like this in code. It creates just one instance of a class. It also lets any part of your code use that same instance.
The Breakthrough Reasons Why Elite Developers Swear By This Pattern
Before diving into code, let’s understand why we might want only one instance of something:
- Saving Resources: Some objects are expensive to create (like database connections)
- Coordination: Sometimes you need one place to manage shared information (like app settings)
- Single Access Point: Having one entry point can simplify your code
For example, think about your app’s settings. You want all parts of your program to use the same settings.
Learn the Singleton Pattern: 3 Simple Steps
Let’s break down how to create a Singleton in TypeScript, starting with the absolute basics:
Step 1: The Problem We’re Solving
Let’s say we’re building an app that needs to connect to a database. Creating new database connections is expensive and we only want one connection that’s shared throughout our application.
Without a Singleton, we might accidentally create multiple connections:
// WITHOUT a Singleton (problematic code)
class DatabaseConnection {
constructor() {
console.log("Creating a new database connection");
// Imagine expensive setup happening here
}
query(sql: string) {
console.log(`Running query: ${sql}`);
return ["results"];
}
}
// This creates TWO separate connections!
const connection1 = new DatabaseConnection();
const connection2 = new DatabaseConnection();
connection1.query("SELECT * FROM users");
connection2.query("SELECT * FROM products");
Every time we use new DatabaseConnection()
we’re creating another connection – not what we want!
Step 2: Creating a Basic Singleton
Now let’s solve this problem step by step:
class DatabaseConnection {
// Step 1: Create a static variable to hold our single instance
private static instance: DatabaseConnection | null = null;
// Step 2: Make the constructor private so no one else can create instances
private constructor() {
console.log("Creating a new database connection");
// Imagine expensive setup happening here
}
// Step 3: Create a method that everyone must use to get the instance
public static getInstance(): DatabaseConnection {
// If an instance doesn't exist yet, create it
if (!DatabaseConnection.instance) {
DatabaseConnection.instance = new DatabaseConnection();
}
// Return the existing instance
return DatabaseConnection.instance;
}
// Normal class methods
public query(sql: string) {
console.log(`Running query: ${sql}`);
return ["results"];
}
}
// How to use our Singleton
const connection1 = DatabaseConnection.getInstance();
const connection2 = DatabaseConnection.getInstance();
// These are actually the same object!
connection1.query("SELECT * FROM users");
connection2.query("SELECT * FROM products");
Let’s break down what’s happening:
- We create a static variable
instance
to store our single instance - We make the constructor
private
so no one can callnew DatabaseConnection()
- We create a public static method
getInstance()
that creates the instance only if needed - When we call
getInstance()
multiple times, we get the same instance back
Step 3: Let’s Prove It Works
How do we know we’re actually getting the same instance? Let’s add some code to check:
const connection1 = DatabaseConnection.getInstance();
const connection2 = DatabaseConnection.getInstance();
// Check if they're the same object
console.log("Are they the same object?", connection1 === connection2);
// This will print: "Are they the same object? true"
// You'll only see "Creating a new database connection" ONCE in the console
When we compare connection1
and connection2
with ===
, we get true
because they’re the exact same object in memory!
Revolutionary Real-World Implementation: The Configuration Manager That Changed Everything
Let’s see a practical example – an app configuration manager that stores settings:
class ConfigManager {
private static instance: ConfigManager | null = null;
private settings: {[key: string]: any} = {};
private constructor() {
console.log("Creating the config manager");
// Set default values
this.settings = {
theme: "light",
fontSize: "medium",
notifications: true
};
}
public static getInstance(): ConfigManager {
if (!ConfigManager.instance) {
ConfigManager.instance = new ConfigManager();
}
return ConfigManager.instance;
}
// Get a setting
public getSetting(key: string): any {
return this.settings[key];
}
// Update a setting
public setSetting(key: string, value: any): void {
this.settings[key] = value;
console.log(`Updated setting: ${key} = ${value}`);
}
}
// Using our ConfigManager
const config = ConfigManager.getInstance();
console.log(`Current theme: ${config.getSetting("theme")}`);
// Update a setting
config.setSetting("theme", "dark");
// Later in another part of the app
const sameConfig = ConfigManager.getInstance();
console.log(`Theme is now: ${sameConfig.getSetting("theme")}`);
// Will print "Theme is now: dark"
Now any part of your app can access or update settings, and everyone will see the same values!
Let me answer three key questions you might ask
Is Singleton Class the same as a global variable?
Kind of, but with important differences:
- It’s encapsulated in a class with proper methods
- It’s only created when first needed
- It follows object-oriented principles
When should I use a Singleton Pattern?
Good times to use a Singleton:
- For managing expensive resources (database connections, file handles)
- For app-wide settings or configuration
- For coordinating activities across your app
- For services that need to maintain state
Are there any downsides to Singletons?
Yes, there are some things to be careful about:
- They can make testing harder
- They can create hidden dependencies in your code
- They can cause problems in multi-threaded applications
- Overusing them can lead to messy code
Wrapping Up
The Singleton pattern works like a key to a room. Only one copy of this key exists. Everyone who uses the key enters the same room. In your code, this lets different parts of your program share resources without making copies.
Remember the three key steps:
- Create a private static instance variable
- Make your constructor private
- Create a public static method to get the instance
Remember, the best developers aren’t those who know how to implement every pattern perfectly they’re the ones who know which pattern fits each problem best.
Now you’re ready to use Singletons in your own TypeScript projects. Happy coding!