Singleton Pattern

What is the Singleton pattern?

Singleton means your program keeps only one object of a type, and every caller gets that same object through one shared door (often getInstance()).

Think of it like a building with one reception desk. Nobody builds a second reception in each hallway. Everyone walks to the same desk.

You usually need three pieces:

  1. Block casual creation — private constructor (or the language’s equivalent).
  2. Store the one instance — in a static field, module variable, or sync.Once.
  3. Expose one accessorgetInstance(), Get(), or a module export.

If those three hold, you have a Singleton.


Why it exists (a short story)

Your checkout flow loads settings from a file. Your reporting job also loads settings. Each does new Config(...).

Months later, checkout reads one timeout and reports reads another. Bugs look random. The real issue is simple: two configs.

Singleton (or “create once and inject everywhere”) is how teams stop that class of bug.

One shared instance
  Checkout ──┐
             ├──▶ getInstance() ──▶ Config (only one)
  Reports  ──┘

Wrong vs right

Wrong: many new calls

Two objects means two truths. Settings drift. Memory and connections get duplicated.

Right: one shared accessor

Callers stop constructing. They ask for “the” config. The type (or the app’s composition root) owns the rule that there is only one.


How to build it

1) Eager creation

Create the instance as soon as the type loads.

Good when: the object is cheap and you always need it.
Watch out: you pay creation cost even if nobody uses it.
In Java this is usually thread-safe because class loading is synchronized.

2) Lazy creation (unsafe if naive)

Create on the first call.

Good when: creation is expensive and maybe unused.
Watch out: in multi-threaded code, two callers can both see “not created yet” and each make an instance. Then Singleton is broken silently.

3) Safe lazy: lock while creating

Good when: you need lazy + safe and the method is not ultra-hot.
Watch out: every call may pay for locking, even after the object exists.

4) Safer lazy: double-check / once

Java note: volatile matters. Without it, another thread can see a half-built object.
Go note: prefer sync.Once — it is the clear, idiomatic tool.
Python/TS note: a module-level instance is often simpler than hand-rolled locks.

5) Holder class / module instance (strong default)

Prefer this pattern when you want lazy + safe without clever locking.
Java: Bill Pugh holder. Go: sync.Once. Python/TS: module-level object.


Worked example: config manager

One place answers “what is db.url?”

Every feature asks the same manager. Settings stay consistent.


When Singleton helps

Use it when a second instance would be wrong:

  • Process-wide settings
  • A single metrics / logging sink for the process
  • A shared resource where duplicates corrupt state

The test is not “would a global be handy?”
The test is: “Would two instances break correctness?”


When Singleton hurts (read this twice)

Singleton is famous — and often overused.

Testing gets harder. If code calls getInstance() deep inside, tests cannot easily swap a fake.

Dependencies go invisible. Readers cannot see what a class needs from its constructor.

Hidden global state makes bugs that depend on call order.

In modern apps, a healthier default is often:

  1. Create one instance at startup.
  2. Pass it in (dependency injection).

You still have one object. You avoid a global door. Prefer Singleton when the type itself must enforce “only one.” Prefer injection when the application decides “we only need one.”


Idea What it solves
Singleton Exactly one instance + one access point
DI / one shared bean One instance, but wired explicitly
Static utility class No instance — only functions (different tool)
Factory Which object to create, not how many

Factory answers “make the right payment type.”
Singleton answers “there must be only one config.”


Practical checklist

  • Prefer holder / once / module instance over locking every call.
  • Use eager creation when the object is tiny and always needed.
  • If you use double-checked locking in Java, never forget volatile.
  • Do not make every service a Singleton.
  • If the type can be cloned or deserialized, close those side doors too.

Exercise

Turn this logger into a safe Singleton. Then prove two lookups are the same object.

Your goals

  1. Outside code cannot freely new extra loggers.
  2. getInstance() / Get() / module export always returns the same object.
  3. Creation is thread-safe (or idiomatic for the language).
Show one solid answer

Quick proof: fetch twice and compare identity (== / is / pointer equality).


Interview questions

Q1. What is Singleton?
A design that keeps one instance of a type and exposes a single way to get it.

Q2. Eager vs lazy?
Eager creates when the type loads. Lazy creates on first use.

Q3. Why is naive lazy unsafe?
Two threads can both observe “missing” and each construct an instance.

Q4. How do you make lazy safe in Java?
Synchronized method, double-checked locking with volatile, or a static holder class. Holder is usually the cleanest class-based option.

Q5. What are the drawbacks?
Harder tests, hidden dependencies, global state. Many teams prefer create-once + inject.

Q6. Singleton vs dependency injection?
Both can yield one instance. DI makes the dependency explicit. Singleton enforces uniqueness inside the type (and often via a global accessor).


What to learn next

Next: Factory Pattern — create the right object without scattering new and type checks everywhere.