Free Interactive Course · Design Patterns

Singleton Design Pattern

One object, one door to reach it — and the surprising cost of that convenience.

Creational Patternseasya.k.a. Single Instance
ShareXLinkedIn
In one sentence

Ensure a class has exactly one instance, and give the rest of the program a single, well-known way to reach it.

01

The problem Singleton solves

The problem

Your service reads its settings from config.yml. Parsing that file takes about 40 ms — trivial, until you notice that every class that needs a setting is doing new ConfigLoader() in its constructor.

Forty classes later you are parsing the same file forty times at boot, holding forty copies of the same map in memory, and — the part that actually pages someone at 3 a.m. — when an operator hot-reloads the config, thirty-nine of those copies are stale.

The same shape shows up everywhere: a database connection pool, a logger, a metrics registry, an in-memory cache. There is genuinely only one of the thing in the real world, but nothing in the code says so.

02

How the Singleton pattern works

The idea is two rules enforced by the class itself, not by a comment asking people to behave:

  1. Nobody outside can construct it. The constructor is private, so new ConfigLoader() stops being a thing you are able to write.
  2. The class hands out the one instance. A static accessor — getInstance(), Instance, instance() — creates it the first time it is asked and returns that same object forever after.

That's the entire pattern. Everything else written about Singleton — holders, Lazy<T>, sync.Once, double-checked locking — is one language's answer to a single follow-up question: what happens if two threads call the accessor at the same moment?

OrderServiceEmailServicegetInstance()getInstance()ConfigLoader- static instance- ConfigLoader()+ getInstance()holds itselfone object · one disk read · one source of truth
Participants. There is only one: the Singleton class. It privately owns its single instance, hides its constructor, and exposes a static accessor. Clients never learn whether the object was created just now or an hour ago.
03

See it: one instance versus many

Click Call ConfigLoader.getInstance() five or six times and watch the heap. Then turn the pattern off and click the same number of times. The counters are the whole lesson.

▶ Try it — one instance vs. many

An interactive heap: calling getInstance() repeatedly creates one ConfigLoader and parses config.yml once, while calling the constructor directly creates a new object and re-parses the file every single time.

Notice what the pattern actually bought you: not speed for its own sake, but the guarantee that every caller is looking at the same map. That's why a config reload works — there is only one thing to reload.
04

Structure and participants

PieceWhat it doesWhy it has to be there
private constructorBlocks construction from outside the class.Without it the pattern is only a suggestion. This is the single line that turns a convention into a compile error.
Static field holding the instanceStores the one object for the lifetime of the process.It has to be static — an instance field would need an instance, which is the thing you're trying to control.
Static accessorCreates the instance on first call, returns it on every call after.The single public door. Every caller goes through it, so you have exactly one place to add logging, locking or a reload hook.
Thread-safety mechanismEnsures two threads racing into the accessor still produce one object.Language-specific: a holder class, Lazy<T>, sync.Once, a function-local static. Skip it and you can end up with two "singletons".
05

Singleton pattern code examples

Pick your language — the tab you choose is remembered across all 23 patterns. These are not translations of one another: each uses what its own language actually considers correct, and where a language makes the classic implementation a bad idea, the panel says so.

A thread-safe, lazily-initialised configuration loader.

public final class ConfigLoader {

    private final Map<String, String> settings;

    private ConfigLoader() {
        // Expensive: parses config.yml off disk. We want this to happen once.
        this.settings = YamlParser.parse(Path.of("config.yml"));
    }

    // The initialization-on-demand holder idiom. Holder isn't loaded until
    // getInstance() is first called, and the JVM guarantees class
    // initialisation is thread-safe — so this is lazy AND safe with no
    // synchronized block and no volatile field.
    private static final class Holder {
        static final ConfigLoader INSTANCE = new ConfigLoader();
    }

    public static ConfigLoader getInstance() {
        return Holder.INSTANCE;
    }

    public String get(String key) {
        return settings.get(key);
    }
}

// If you don't need laziness, `public enum ConfigLoader { INSTANCE; ... }` is
// shorter and is the only form the JVM defends against reflection and
// deserialisation attacks (Effective Java, item 3).
Read across the tabs and the real lesson appears: the pattern is constant, the mechanism is not. Java hides the laziness in a holder class, C++ leans on a language guarantee, C# buys it from Lazy<T>, Go uses sync.Once, and JavaScript and Python barely need the pattern at all because their module systems already do it. If a tutorial shows you the same code in seven languages, it is teaching you Java with different keywords.
06

How to implement Singleton

  1. Make the constructor private (or, in Go and Python, keep the type unexported and the constructor internal by convention).
  2. Add a static field to hold the single instance.
  3. Add a static accessor that returns it, creating it on the first call if you want lazy initialisation.
  4. Make that creation safe against concurrent callers using whatever your language provides — a holder class, Lazy<T>, sync.Once, or a function-local static. Do not hand-roll double-checked locking.
  5. Block the escape hatches: delete the copy constructor in C++, mark the class final/sealed, and be aware that reflection and deserialisation can still forge a second instance in Java unless you use an enum.
  6. Before you ship it, ask whether a plain object passed in through the constructor would do the same job. Most of the time the honest answer is yes — see the next section.
07

When to use Singleton — and when not to

Good reasons

Reach for it when there genuinely is only one of the thing and creating a second would be wrong, not merely wasteful: a connection pool, a metrics registry, a logging backend, an in-process cache, a hardware device handle. The tell is that two instances would disagree with each other.

Why senior engineers flinch

It's a global variable wearing a jacket. A static accessor can be called from anywhere, so any class can quietly acquire a dependency without it appearing in the constructor. Six months later nobody can tell what a class actually needs by reading its signature.

It makes tests share state. One test mutates the singleton, the next test reads it, and now your suite passes or fails depending on the order it runs in. Because the constructor is private, you usually cannot substitute a fake either.

The lifetime is the process, not the request. Anything you cache in a singleton lives until restart — which is exactly what you want for a connection pool and exactly what you don't want for anything user-specific.

The alternative worth knowing

Most modern codebases keep the one instance and throw away the static accessor: create the object once at startup and pass it in through constructors, or register it with a DI container as a singleton-scoped service. You still get one connection pool; you also get a class whose dependencies are visible in its signature and replaceable in a test. That is why services.AddSingleton<T>() and Spring's default singleton bean scope exist — same guarantee, none of the global.

SituationUse classic Singleton?Better move
A logger or metrics registry used from everywhereReasonableFine as-is — the global reach is the point, and it's stateless enough not to poison tests.
A database connection poolRarelyOne instance created in main(), injected where needed. Tests get their own.
Application configurationSometimesLoad once, inject the resulting immutable object. Injection makes per-test config trivial.
Anything holding per-user or per-request stateNoYou are about to leak one user's data into another's request. Use request scope.
"It's just easier to reach from here"NoThat's the global-variable urge, not a design decision. Pass it in.
08

Quick check

🧠 Quick check
You write a Singleton with a plain if (instance == null) instance = new Foo(); accessor, in Java. Two threads call it at the same instant. What can go wrong?

In the wild

Javajava.lang.Runtime.getRuntime() — one Runtime per JVM, the textbook example.
Pythonlogging.getLogger("app.db") returns the same logger object for a given name, every time — a keyed singleton hiding in the standard library.
C#services.AddSingleton<IClock, SystemClock>() — the same one-instance guarantee, delivered by injection rather than a static property.
Gohttp.DefaultClient and sql.Register's driver map — package-level state initialised once, reached through package functions.
C++std::cout — one global stream object per process, constructed before main() runs.
JavaScriptEvery ES module you import. import fs from 'node:fs' gives every file in the process the identical object.

Frequently asked questions

Is the Singleton pattern an anti-pattern?
Not inherently — but it is the pattern most often reached for the wrong reason. The problem is rarely the "one instance" guarantee, which is frequently correct; it's the static global accessor that comes bundled with it, because that hides dependencies and makes tests share state. Modern practice usually keeps the single instance and drops the static access by registering the object as a singleton-scoped dependency instead.
How do I make a Singleton thread-safe?
Use what your language already provides rather than hand-rolling it: the initialization-on-demand holder idiom or an enum in Java, Lazy<T> in C#, a function-local static in C++11 or later, and sync.Once in Go. Double-checked locking is famous mostly because it was subtly broken in Java before version 5, and it is still easy to get wrong; none of these languages requires you to write it today. JavaScript and TypeScript need no guard at all — the runtime is single-threaded.
How do you unit test code that uses a Singleton?
With difficulty, which is the strongest practical argument against the classic form. The usual options are to add a package-private reset hook for tests (fragile, and it leaks test concerns into production code), to have the singleton hold an interface you can swap out, or — the option that actually works — to stop using the static accessor and inject the instance instead, so a test simply passes a fake.
What's the difference between a Singleton and a static class?
A Singleton is an object, so it can implement interfaces, be passed as an argument, be subclassed, and be created lazily. A static class is none of those things — it's a namespace for functions. If you will never need polymorphism or lazy initialisation, a static utility class is simpler and more honest; the moment you want to substitute the thing in a test, you needed an object.
Can a Singleton be broken in Java?
Yes. Reflection can call a private constructor via setAccessible(true), deserialisation can produce a second instance unless you implement readResolve(), and a class loaded by two different class loaders gives you two independent "singletons". The single-element enum form is the only one the JVM defends against reflection and serialisation for you.

Finish the handbook, earn the certificate

All 23 Gang-of-Four patterns, each with a simulator you can click and code in seven languages. Free, no signup.

See all 23 patterns →
Found this course useful? Share it.
ShareXLinkedIn

Comments

0

Join the conversation. Sign in to leave a comment — we'd love to hear your thoughts.