Singleton Pattern: One Principal for the Whole School
Understand the Singleton design pattern with a school principal story, simple TypeScript and C# code, thread safety, and why many seniors call it an anti-pattern.
๐ฏ One school, one principal
Welcome to Green Valley School. It has hundreds of students, dozens of teachers, many classrooms, two playgrounds โ but how many principals?
Exactly one. Her name is Principal Meena Madam.
There is one Principal Madam, one principal's office on the first floor, and one official notice board outside that office, polished daily by Raju bhaiya, the school peon. When the school needs an important decision โ exam dates, holiday announcements, permission for the annual function โ everyone goes to that same office. Anita Madam (maths) goes there. Vikram Sir (sports) goes there. Even little Pinky from Class 6A goes there when she wants permission for the science club. Nobody says, "Let me quickly appoint my own second principal for my class." Two principals announcing two different exam dates? Total chaos. Impossible!
Notice three things about how Green Valley manages this:
- You cannot create a principal yourself. There is no shop where Pinky can "buy" a new principal. The post is protected.
- There is one well-known way to reach the principal. Everyone knows where the office is. You do not need someone to personally introduce you.
- Whoever asks, gets the same principal. Class 6A and Class 10B both reach the very same person, the very same notice board.
This is the Singleton pattern in real life. One special object for the whole program, creation strictly controlled, and a single famous "door" through which everyone reaches it.
The same idea sits on the roof of Pinky's apartment building, Shanti Heights: one water tank serves every flat. Flat 101 and flat 504 open different taps, but the water comes from the same tank. If flat 302 wastes water, flat 101 feels it. One electricity meter per house (two meters would double-count your units!), one captain for a cricket team, one water tank per building โ some things are correct only when there is exactly one.
Here is a normal Tuesday at Green Valley, as a journey:
Hold on to this school story. We will write the principal's office in code soon. And later โ this is important โ we will also discuss honestly why many senior developers say, "Use this pattern very, very carefully."
๐ก What is the Singleton pattern?
The Singleton pattern is a creational design pattern. Here is its plain definition:
Singleton guarantees that a class has only one instance during the whole life of the program, and it provides a single global access point (usually a static method like
getInstance()) to reach that instance.
So the class takes on two jobs at once:
- It controls its own creation. The constructor is hidden (made
private), so no outside code can writenew Principal(). The class creates its one instance itself โ just like the school board, not the students, appoints Meena Madam. - It provides access. A static method or property hands out that one instance to anyone who asks, from anywhere in the program โ the famous office door on the first floor.
One-line memory trick: "One post, one person, one office door." Singleton = private constructor + static instance + public getInstance().
Singleton is one of the 23 classic patterns from the Gang of Four book (1994). It is the easiest pattern to learn and, funnily, also the most criticised pattern ever โ we will see both sides today.
College corner: notice that Singleton quietly violates the Single Responsibility Principle by design. The class manages its own lifetime (job 1) and does its real work (job 2). That double duty is the seed of most later complaints: lifetime management belongs to the application's composition root or a container, not to the class itself. Keep this thought โ it returns in the anti-pattern section.
โ ๏ธ The problem it solves
Why would anyone need such a strange class? Two real needs push developers here.
Need 1: Some things must exist only once.
Imagine a program where every part creates its own database connection pool:
// The dangerous "everyone makes their own" way
class ReportService {
private pool = new ConnectionPool(); // pool #1 (100 connections)
}
class BillingService {
private pool = new ConnectionPool(); // pool #2 (another 100!)
}
class EmailService {
private pool = new ConnectionPool(); // pool #3 (!!)
}
// The database planned for 100 connections... now faces 300.
// It starts rejecting connections. The app crashes at peak time.A plain constructor cannot say "no" to a second call. Every new gives a fresh object. But for some resources โ the log file, the parsed configuration, the connection pool, the cache โ a second copy is not just wasteful, it is wrong. Two log objects writing to one file can mix their lines. Two settings objects can disagree with each other. Two principals can announce two different exam dates. Two water tanks on one roof with one inlet pipe? The plumber will laugh at you.
Need 2: That one thing must be reachable from many places.
The logger is needed in almost every file. Passing it by hand through fifty layers of constructors feels tiring. A global variable would be reachable โ but globals are fragile: any code can overwrite them, and nothing guarantees they are initialised before first use.
Singleton tries to answer both needs in one stroke: hide the constructor (so only one instance can ever exist) and expose a static access door (so everyone can reach it).
How big is the waste, really? Look at the open connections when every service builds its own heavy pool versus everyone sharing one:
Ten services, each opening a 100-connection pool, means one thousand connections hammering a database sized for one hundred. The shared pool keeps it at exactly one hundred. This chart is the whole business case for "exactly one instance".
๐ ๏ธ How it works, step by step
The recipe is small. You can write it in any language in five steps:
- Make the constructor
private. This locks the front door. Outside code can no longer writenew Principal()โ the compiler itself will refuse, like the school board rejecting Pinky's application to appoint her own principal. - Add a private static field inside the class to hold the single instance. It starts empty (
null). - Add a public static method โ traditionally named
getInstance()โ that everyone will call. - Inside
getInstance(), create lazily. On the very first call, the instance does not exist yet, so create it and store it in the static field. On every later call, just return the stored one. "Lazy" means created only when first needed, not at program start. - Make it thread-safe if needed. If two threads call
getInstance()at the exact same moment when the instance is still empty, both can pass the "is it null?" check and create two instances โ breaking the whole promise! Languages give safe tools for this (we will see C#'sLazy<T>below).
Note the funny self-arrow: the class holds an instance of itself in a static field. That is the signature of Singleton.
Watch what happens when two teachers visit, one after the other:
The whole lifetime of a singleton has only three interesting moments โ not created yet, created on first demand, and then the same instance returned forever:
Look at the loop on SameInstanceReturned in Figure 6 โ that loop is the entire promise of the pattern. After the first call, nothing new is ever created. And notice there is no arrow back to NotCreated: a classic singleton, once born, lives until the program dies. Remember this โ it is also why tests struggle with it later.
๐งช Real-life code example: the principal's office in TypeScript
Let us code Green Valley School. Follow the comments.
// The one and only principal's office.
class PrincipalOffice {
// Step 2: the private static field holding the single instance.
// It is empty until someone first needs the office.
private static instance: PrincipalOffice | null = null;
// The official notice board (the office's state).
private notices: string[] = [];
// Step 1: PRIVATE constructor. Nobody outside can call `new`.
private constructor() {
console.log("๐ซ Principal's office is being set up (runs only ONCE)...");
}
// Step 3 + 4: the public static door, with LAZY creation.
public static getInstance(): PrincipalOffice {
if (PrincipalOffice.instance === null) {
// First visitor ever โ set up the office now.
PrincipalOffice.instance = new PrincipalOffice();
}
// Every visitor, first or later, gets the SAME office.
return PrincipalOffice.instance;
}
public announce(notice: string): void {
this.notices.push(notice);
console.log(`๐ข New notice: ${notice}`);
}
public readNotices(): string[] {
return [...this.notices];
}
}
// ---------- Client code: a normal school day ----------
// Anita Madam visits the office.
const officeForMaths = PrincipalOffice.getInstance();
officeForMaths.announce("Maths exam on Monday, 9 AM.");
// Vikram Sir visits "another" office... or does he?
const officeForSports = PrincipalOffice.getInstance();
officeForSports.announce("Annual sports day on 26 January.");
// Are these two different offices? Let's check!
console.log("Same office?", officeForMaths === officeForSports);
// Pinky reads the board โ sees BOTH notices,
// because there is only ONE board in ONE office.
console.log("Notice board:", officeForSports.readNotices());
// And if someone naughty tries: new PrincipalOffice();
// โ TypeScript error: Constructor of class 'PrincipalOffice' is private.Expected output:
๐ซ Principal's office is being set up (runs only ONCE)...
๐ข New notice: Maths exam on Monday, 9 AM.
๐ข New notice: Annual sports day on 26 January.
Same office? true
Notice board: [ 'Maths exam on Monday, 9 AM.', 'Annual sports day on 26 January.' ]Read the output carefully โ it proves all three promises:
- The setup message printed only once, even though
getInstance()was called twice. That is lazy creation: built on the first call, reused afterwards. officeForMaths === officeForSportsistrueโ both variables point to the same object in memory.- The notice from Anita Madam is visible to Vikram Sir and to Pinky, because everyone shares one notice board.
โ ๏ธ What about two visitors at the exact same moment?
JavaScript and TypeScript normally run your code on a single thread, so two calls cannot truly collide โ our simple if (instance === null) check is safe there.
But in languages like C#, Java, or C++, many threads run at once. Picture this race:
Thread A: checks instance โ it is null โโ
Thread B: checks instance โ it is also null โโค both passed the check!
Thread A: creates office #1 โ
Thread B: creates office #2 โ โ TWO principals now! ๐ฑTwo offices, two notice boards, total confusion โ exactly what Singleton promised to prevent. The classic fixes are:
- Eager creation: build the instance at class-load time, before any thread can race. Simple and safe, but you lose laziness (it is built even if never used).
- Lock every call: take a lock before checking. Safe, but every call pays the locking cost forever.
- Double-checked locking: check first without the lock, lock only if still empty, check again inside. Fast โ but famously easy to get subtly wrong without memory barriers.
College corner โ why double-checked locking burned experts: the danger is not the logic, it is the memory model. The line instance = new PrincipalOffice() is not one atomic action; the compiler or CPU may reorder it into (1) allocate memory, (2) publish the pointer, (3) run the constructor. A second thread can then observe a non-null instance whose constructor has not finished โ and happily use a half-built object. Java fixed this only when volatile got stronger semantics in the Java 5 memory model (JSR-133); C# needs volatile or careful use of Interlocked for the same reason. The safe modern idioms push the problem onto the runtime: C#'s Lazy<T> (which handles safe publication internally), Java's class-holder idiom (the JVM guarantees class initialisation is both lazy and thread-safe), Java's enum singleton, Go's sync.Once, Rust's OnceLock. The lesson generalises beyond Singleton: never hand-roll one-time initialisation from memory; use the language's blessed primitive.
The modern advice in one line: do not hand-roll this. Which brings us to C#.
๐งช The same idea in C# โ thread-safe with Lazy<T>
C# gives a beautiful one-liner for safe lazy singletons. Lazy<T> guarantees the factory runs exactly once, even if a hundred threads ask at the same instant.
public sealed class PrincipalOffice
{
// Lazy<T> creates the instance on FIRST access,
// and .NET guarantees thread safety for us. No locks to write!
private static readonly Lazy<PrincipalOffice> _lazy =
new(() => new PrincipalOffice());
public static PrincipalOffice Instance => _lazy.Value;
private readonly List<string> _notices = new();
// Private constructor: outside code cannot use `new`.
private PrincipalOffice()
{
Console.WriteLine("Principal's office set up (only once).");
}
public void Announce(string notice) => _notices.Add(notice);
public IReadOnlyList<string> ReadNotices() => _notices;
}
// Usage โ from anywhere, on any thread:
PrincipalOffice.Instance.Announce("PTM on Saturday at 10 AM.");
var a = PrincipalOffice.Instance;
var b = PrincipalOffice.Instance;
Console.WriteLine(ReferenceEquals(a, b)); // True โ the same officesealed stops anyone from subclassing the singleton (a subclass could sneak in a second instance). static readonly plus Lazy<T> handles the entire thread-safety puzzle in one line. This is the idiomatic modern C# singleton.
And for completeness, the Python flavour. In Python, the most honest singleton is simply a module โ Python imports every module exactly once and caches it, so module-level objects are natural singletons:
# water_tank.py โ the module IS the singleton.
# Python runs this file only once, no matter how many files import it.
class _WaterTank:
def __init__(self):
self.level = 1000 # litres
def use(self, litres: int) -> None:
self.level -= litres
def refill(self, litres: int) -> None:
self.level += litres
# The one tank on the roof of Shanti Heights:
tank = _WaterTank()
# Any other file:
# from water_tank import tank
# tank.use(50)
# Everyone gets the same tank object โ guaranteed by the import system.No private constructor gymnastics needed. The import system already gives "create once, share everywhere". (Python folks also discuss the Borg/monostate idea in faif/python-patterns โ many objects sharing one state โ a fun cousin with the same dangers.)
๐ก Where you see it in real software
Singleton (and "singleton-like" single instances) appear all over real systems:
- Application configuration. A program parses its settings file once and shares the result everywhere. Parsing twice wastes time; two disagreeing configs would be a bug.
- Loggers. Logging frameworks like Java's Log4j or Python's
loggingmodule hand you the same logger object for the same name every time โ one shared writer per log destination, so log lines do not get mixed up. - Database connection pools. The pool is sized to the database's limits, so there must be exactly one. Creating pools per-class is the classic outage story (remember Figure 3).
- Spring Framework beans (Java). Here is the interesting twist: in Spring, singleton is the default scope for every bean โ one instance per container โ but the container manages it and injects it into your classes. You get single-instance behaviour without writing
getInstance()anywhere. This is widely considered the right modern way (more on this below). - ASP.NET Core services. Same idea:
services.AddSingleton<MyService>()registers a class with singleton lifetime in the DI container. One instance, but delivered through constructors, not global statics. - Runtime objects. Java's
Runtime.getRuntime()is a textbook Singleton shipped inside the JDK itself. - Learning repositories. The iluwatar/java-design-patterns repo shows five Java variants (eager, lazy, double-checked, holder idiom, enum).
Which of these are truly unique by nature? In a typical backend codebase, the honest split looks something like this:
That last slice is the painful one โ objects that someone made a singleton only for convenience. The next two sections are about exactly that slice.
๐ When to use it and when not to
| Situation | Use Singleton? |
|---|---|
| A truly unique, process-wide resource (one log file, one parsed config, one connection pool) | โ Maybe โ but prefer DI with singleton lifetime (see below) |
| Small script or throwaway tool where wiring DI is overkill | โ Yes โ pragmatic and fine |
| Mostly read-only, set-once values needed everywhere | โ Acceptable โ immutable singletons cause far less trouble |
| "Passing this object around feels boring" | โ No โ laziness is not a design reason; hidden access will cost you later |
| The class holds mutable state used by many modules | โ Dangerous โ invisible coupling between far-away parts of code |
| You will write unit tests for code using this class | โ Avoid classic Singleton โ tests cannot swap in a fake easily |
| You "might need more than one someday" (one per tenant, one per region) | โ No โ Singleton hard-codes "exactly one" and is painful to undo |
| Big, long-lived team project | โ Prefer dependency injection with singleton-scoped registration |
Want it as a picture? Judge your case on two axes: how truly unique the resource is by nature, and how much its state changes:
A shopping cart as a singleton (bottom-left) is the classic disaster: every user would share one cart. A parsed, read-only config (top-right) is about as safe as singletons get.
โ ๏ธ Why many seniors call Singleton an anti-pattern
Now the honest part. Ask any experienced developer about Singleton and you will see a careful frown. Singleton is the only GoF pattern that is routinely called an anti-pattern โ a solution that looks helpful but quietly creates bigger problems. Important: the criticism is not against the idea of "one instance" (that is sometimes genuinely required โ one water tank really is correct for one building). It is against the technique: a class giving global static access to itself.
Here are the four big complaints, in simple words:
1. It is global state in a fancy coat. For decades, programmers learned to avoid global variables โ anything can read or change them from anywhere, so the program becomes impossible to reason about locally. A mutable singleton is exactly a global variable wearing a class costume. Change it in module A, and module Z mysteriously behaves differently. Debugging such "spooky action at a distance" eats whole evenings. In Shanti Heights language: if anyone in any flat can secretly open the tank's outlet valve, no single flat can ever explain why the water pressure dropped.
2. It hides dependencies. Look at this function:
function generateReportCard(student: Student): ReportCard {
// ...looks like it depends only on `student`...
const config = SchoolConfig.getInstance(); // surprise dependency!
const logger = Logger.getInstance(); // another surprise!
// ...
}The signature says "give me a student, I give you a report card." It lies. The function secretly reaches out to two singletons. A reader cannot know the true dependencies without reading every line of the body. Constructors and parameters stop being honest contracts.
3. It makes testing painful. Good unit tests want a fresh, isolated world for every test. But a singleton is shared static state that survives between tests โ remember Figure 6: there is no arrow back to NotCreated. Test 1 posts a notice; test 2 unexpectedly sees it; tests pass alone but fail together, in confusing order-dependent ways. Worse: you usually cannot substitute a fake. If OrderService internally calls PaymentGateway.getInstance(), then testing "place order" hits the real payment gateway. Ouch.
4. Thread-safety is easy to get wrong. As we saw, hand-rolled lazy creation has a race condition, and double-checked locking trapped even expert programmers for years. And after creation, the singleton's own mutable state still needs its own synchronisation โ one notice board scribbled on by fifty threads is still a mess, even if there is only one board.
The honest summary: classic Singleton = global mutable state + hidden dependencies + hard testing. Use it knowingly, in small doses, in small programs. In serious projects, reach for dependency injection first. If a senior reviews your code and sees getInstance() sprinkled everywhere, expect questions โ and they will be fair questions.
So what should you reach for instead? Here is the menu, from simplest to most powerful:
| Alternative | How it works | When it fits |
|---|---|---|
| Manual dependency injection | Create the one instance in main(), pass it through constructors | Small and medium apps; always a safe default |
| DI container with singleton lifetime | Register once (AddSingleton, Spring bean); the container injects it everywhere | Team projects, web backends โ the modern standard |
| Module-level instance (Python, JS/TS modules) | The module system itself guarantees one shared instance per process | Scripts and apps in module-based languages |
| Plain function parameter | Just pass the object to the few functions that need it | When only two or three call sites need it โ simplest of all |
| Monostate / Borg | Many instances, one shared state | Rarely; mostly a curiosity โ it shares Singleton's downsides |
| Honest global constant | An exported immutable value | Read-only config-like data; immutability removes most danger |
๐ก The better way: dependency injection
Here is the wonderful news โ you can keep the benefit ("only one logger, one config, one pool") and drop almost all the costs. The recipe is called dependency injection (DI), and it is simpler than its scary name:
- Create exactly one instance near the program's starting point (the
mainfunction โ often called the composition root). - Pass it into the classes that need it, through their constructors.
// At startup โ created ONCE, like appointing one principal:
const config = new SchoolConfig("settings.json");
const logger = new Logger(config);
const office = new PrincipalOffice(logger);
// Dependencies are now EXPLICIT and visible in constructors:
const examService = new ExamService(office, logger);
const sportsService = new SportsService(office);There is still only one PrincipalOffice โ but now:
ExamServicedeclares openly what it needs. No lies in the signature.- In a test, you can hand it a
FakePrincipalOfficein one line. Testing becomes easy. - No global static state exists at all.
The school board (your main function) appoints Meena Madam once and introduces her to every department. No teacher conjures a principal out of thin air, yet everyone works with the same one.
College corner โ "singleton" becomes a lifetime, not a pattern: DI containers (Spring, ASP.NET Core, Angular's injector, NestJS) turn "how many instances exist" into configuration. In ASP.NET Core, AddSingleton, AddScoped (one per web request), and AddTransient (new every time) are just three lifetime choices for the very same class. The class itself stays plain โ public constructor, explicit dependencies, fully testable โ and the container enforces uniqueness at the composition level. This separation (class logic vs lifetime policy) is the textbook fix for the SRP violation we flagged in the first College corner. One caution from real projects: a singleton-scoped service must never hold per-user or per-request state, and it must be thread-safe, because all requests share it. The pattern's old dangers do not vanish; they just move into a smaller, well-labelled box.
โ ๏ธ Common mistakes students make
Mistake 1: Using Singleton just to avoid passing parameters. "Calling getInstance() is easier than passing the object" is the most common trap. The short-term ease becomes long-term hidden coupling. If a class needs something, say so in its constructor.
Mistake 2: Forgetting the race condition in multi-threaded languages. A plain "if instance is null, create it" is fine in single-threaded JavaScript, but in C#, Java, or C++ two threads can both pass the check and create two instances. Use Lazy<T>, sync.Once, the holder idiom, or eager initialisation โ never hand-rolled double-checked locking from memory.
Mistake 3: Confusing Singleton with a static class. A static class is just a bundle of functions: it cannot implement an interface, cannot be passed as a parameter, cannot be swapped. A singleton is a real object โ it can do all of those. If you find yourself making everything static "for convenience", pause and rethink.
Mistake 4: Stuffing the singleton with unrelated jobs. Because the singleton is reachable from everywhere, it slowly becomes the dumping ground: config + cache + counters + helper functions, all in one giant class. This violates the Single Responsibility Principle twice over โ the class already does two jobs (its real work + managing its own lifetime), do not add a third and fourth.
๐ Compare with cousins
Singleton sits among the creational patterns, but its goal is the opposite of most of them โ others help you create many objects flexibly; Singleton stops you from creating more than one!
| Pattern | Main question it answers | How many instances? | Key difference from Singleton |
|---|---|---|---|
| Singleton | "How do I guarantee only ONE instance?" | Exactly one | โ |
| Prototype | "How do I get another one like this?" | As many copies as you want | Opposite goal: Prototype multiplies, Singleton restricts |
| Factory Method | "Which subclass decides what gets created?" | Many | Creates new objects each call; can return a singleton if it wants |
| Abstract Factory | "How do I create families of related objects?" | Many | The factory object itself is often implemented as a Singleton |
| Builder | "How do I assemble a complex object step by step?" | Many | Builds fresh complex objects; nothing to do with uniqueness |
| Flyweight | "How do I share many small immutable objects to save memory?" | Many shared, deduplicated | Flyweights are many immutable shared objects; Singleton is one (often mutable) object |
One more relative worth naming: Facade. A facade object (a simple front-door to a complex subsystem) is very often made a Singleton, because one front door per subsystem is enough.
To revise the whole topic in one glance, here is Singleton as a mind map:
๐ฏ Quick revision box
+--------------------------------------------------------------+
| SINGLETON PATTERN โ REVISION |
+--------------------------------------------------------------+
| Idea : Exactly ONE instance + one global access door |
| Story : One principal, one office, one notice board |
| (and one water tank for the whole building) |
| |
| Recipe : 1. private constructor (lock the front door) |
| 2. private static instance field |
| 3. public static getInstance() |
| 4. lazy creation on first call |
| 5. thread-safety (Lazy<T> / sync.Once / eager) |
| |
| Proof : getInstance() twice -> SAME object (a === b) |
| Lifecycle : NotCreated -> Created -> SameInstanceReturned |
| |
| Real world : config, logger, connection pool, |
| Spring beans (DI-managed!), Runtime.getRuntime |
| |
| โ ๏ธ Honest warning โ why seniors frown: |
| - global mutable state in disguise |
| - hidden dependencies (signatures lie) |
| - tests cannot swap a fake; state leaks between tests |
| - thread-safe lazy init is easy to get wrong |
| |
| Better way : dependency injection โ create once at startup, |
| PASS it in; DI container singleton scope |
+--------------------------------------------------------------+๐งช Practice exercise
Three tasks, from easy to thought-provoking โ Pinky-level to Meena-Madam-level. Try them before peeking back!
-
The water tank. Write a
WaterTanksingleton in TypeScript with a private constructor, agetInstance()method, and alevel(litres) starting at 1000. Adduse(litres)andrefill(litres)methods. From three different "flats" (three variables obtained viagetInstance()), use some water, then print the level from a fourth variable. It must show the combined effect โ proving everyone shares the one tank on the roof. -
Break it, then fix it. Take your
WaterTankand pretend two threads callgetInstance()at the same time when the instance is null. Write down, step by step on paper, how two tanks could get created. Then write the C# version usingLazy<WaterTank>and explain in two sentences why the race disappears. Bonus for college students: explain why a naive double-checked lock could still hand out a half-constructed tank without proper memory barriers. -
The DI makeover (the most important one). Write a
BillingServicethat first usesWaterTank.getInstance()internally. Then refactor: removegetInstance()from inside the service and instead accept the tank through the constructor โnew BillingService(tank). Finally, write a tinyFakeWaterTankand show how the refactored version lets you testBillingServicewithout touching the real tank. Feel the difference โ that feeling is why seniors prefer dependency injection.
If task 3 made sense to you, congratulations โ you now understand not only the Singleton pattern but also its biggest criticism and its modern replacement. That is more than many working programmers can say. Shabash, keep going!
Frequently asked questions
- What is the Singleton pattern in one line?
- Singleton is a creational pattern that makes sure a class has exactly one instance in the whole program, and gives everyone a single well-known way to reach it, usually a static getInstance() method.
- How does Singleton stop people from creating a second object?
- The class makes its constructor private, so outside code cannot call new. The class itself creates the one instance and hands it out through a static method or property.
- Why do many developers call Singleton an anti-pattern?
- Because it creates hidden global state. Dependencies become invisible, unit tests cannot easily replace the singleton with a fake, and state leaks between tests. Dependency injection gives the same one-instance benefit without these problems.
- What is lazy initialization in Singleton?
- Lazy means the instance is created only on the first call to getInstance(), not at program start. It saves startup time, but in multi-threaded programs you must guard the creation so two threads do not create two instances.
- Is Singleton the same as a static class?
- No. A static class is just a bag of functions and can never be passed around or implement an interface. A singleton is a real object, so it can implement interfaces, be passed as a parameter, and (in theory) be swapped for another implementation.
- What should I use instead of Singleton in big projects?
- Create one instance at the start of the program and pass it into the classes that need it (dependency injection). DI containers can even register a class with a singleton lifetime, so you get one instance without global static access.
Further reading
Related Lessons
Factory Method Pattern: Let the Branch Decide the Vehicle
Learn the Factory Method design pattern with a simple Indian tiffin service story, easy TypeScript and C# code, diagrams, real examples, and practice.
Abstract Factory Pattern: One Order, One Matching Thali
Understand the Abstract Factory design pattern through an Indian wedding catering story, with simple TypeScript and Python code, diagrams, and practice.
Builder Pattern: Stitch It Step by Step, Like a Master Tailor
Learn the Builder design pattern with an Indian tailor shop story, fluent TypeScript and C# code, the telescoping constructor problem, and practice tasks.
Prototype Pattern: Make Copies Like a Xerox Machine, Not From Scratch
Learn the Prototype design pattern with a simple wedding card story, easy TypeScript and Python examples, and a clear shallow vs deep copy demo.