Course contents

Singleton — honestly

Sign in to run this lab and save your progress

Learn

Before you start

Singleton is the pattern most often used, and most often used WRONG. Read this before you write any code.

The most important thing

In Python, most singletons should just be module-level objects, not classes with __new__ tricks.

# printer.py
class Printer: ...
printer = Printer()   # one line. Done.

Every from printer import printer returns the same object. Python's import system has been guaranteeing "one object per program" since 1991. You don't need a pattern for that.

Use a real Singleton class only when you need:

  • The GUARANTEE that Printer() can't build a second object (for real requirements the app has to enforce), OR
  • Runtime-picked behavior (Printer.instance() picks the right kind based on the environment)

If you just want "one shared object" — use a module.

When Singleton is a fair choice

Actually rare list:

  • A physical resource with only one. Real printer, real serial port, real GPU on an embedded system.
  • A rule enforced by law or math. "There is one system clock" (kind of — datetime doesn't actually enforce this).
  • A legacy library that assumes there's only one. You're wrapping it.

If your reason is "I only need one" — that's a module. Not a Singleton.

When Singleton is a mistake

  • App config. Use a module-level Config object. Or pass a config into the services that need it.
  • Database connection. Use a module-level pool. Or pass one in.
  • Logger. Python's logging module already handles this correctly, without exposing a Singleton class.
  • Cache. Same story.
  • Whatever you were about to say. Really.

The internet is full of tutorials that have students writing 20-line Singleton implementations for things Python already handles in one line. Don't be that engineer in a code review.

The test problem

Singletons are the #1 cause of flaky test suites. Tests share state through the singleton, one test's setup poisons another, and debugging takes hours.

Fixes:

  • Add a _reset() method — every test resets it first. (We do this in the lesson.)
  • Use a pytest fixture that auto-resets with autouse=True.
  • Or don't use Singleton at all — pass the printer in from outside.

The old "there's only ONE production instance" argument falls apart the moment you write tests. Tests want fresh state. Singletons make that hard.

Compared to module-level state

For the same problem, module-level would look like this:

# printer.py
class Printer:
    def __init__(self): self._jobs = []
    def submit(self, job):
        self._jobs.append(job)
        return f"queued {job} (position {len(self._jobs)})"
    def jobs(self): return tuple(self._jobs)

_printer = Printer()

def printer():
    return _printer

def _reset():
    global _printer
    _printer = Printer()

Same behavior. Half the code. Fewer traps. In Python this is almost always the right answer.

The class-based Singleton in this lesson is here so you can recognize it (interviewers ask), write it correctly, and KNOW when NOT to reach for it. That third one is the interview skill.

The decision cheat-sheet

For "should this be a singleton?":

Situation Answer
"Only one exists in production" Module-level object
"Building two would corrupt state" Class Singleton with .instance() (this lesson)
"Building two would waste memory" Not a design concern — probably still module-level
"Multiple customers share the process, each needs its own" NOT Singleton — pass it in
"Tests need fresh state each run" Not Singleton, or Singleton with _reset()
"Config that changes per environment" Pass a Config object in

What the tests in this lesson do

  • Shared state across callers — the singleton payoff.
  • _reset() works — the escape hatch for tests.
  • Direct Printer() still allowed — Design D's advantage over the __new__ version. Tests that want isolation don't have to poke inside the class.

In an interview

If asked "should this be a Singleton?" — pause. Ask the requirement questions from the objective. If the answer is "I need one shared object", say "in Python I'd use a module-level object rather than the Singleton pattern. Here's why...". That answer shows judgment.

If the interviewer specifically wants the Singleton pattern — write it. Show them the classmethod version (Design D). Mention that in real Python you'd usually just use module-level state. They'll respect the honesty.

Your task

Singleton is the most over-taught, over-used pattern in the GoF book. Half the internet's tutorials start with it because it's the shortest to explain. Almost every real use of it is wrong.

This lesson teaches Singleton honestly — including when NOT to use it, which in Python is most of the time.

The problem

A print-shop has ONE physical printer. Every module that talks to the printer must talk to the SAME object — otherwise two jobs collide and paper jams.

Requirements:

  • Exactly one Printer object exists in the whole program.
  • Every caller who asks for the printer gets THE SAME object.
  • You can't accidentally build a second one.

Sounds like "clearly Singleton". Don't rush to say yes.

Questions to ask about the requirements

Before using Singleton, ask:

  1. Is "only one" a REAL requirement of the problem, or just convenient? One physical printer → real requirement. One config object because "why would you have two" → just convenient. Real requirements are much stronger.
  2. Will there EVER be a second one? For testing? For multiple customers sharing the process? For process forks? If yes → Singleton makes those cases painful.
  3. What about tests? Every test that touches a Singleton shares state with every other test — tests have to be careful to reset it. Convenience singletons make test suites flaky.
  4. Would a MODULE-level object work? In Python, import printer gives you the same object everywhere, automatically. No classes needed.
  5. Would just passing the object in as an argument work? More typing at every call site, but tests become much easier.

If (1) says "real requirement" and (2) says "never two" → Singleton is defensible. Anything else → use a module-level object, dependency injection, or just don't use Singleton at all.

Different designs, compared

Design A — module-level object (Python's default)

# in printer.py
class Printer:
    def print(self, doc): ...

printer = Printer()   # created once when the module loads

Callers: from printer import printer.

  • Good: Zero pattern boilerplate. Python's import system already guarantees the module runs once, so printer is basically a singleton for free. Easy to test — a test can just replace the printer variable with a fake.
  • Bad: Nothing stops a caller from writing Printer() and getting a fresh, separate object. Also, code runs when you import the module (which can surprise people).
  • When it wins: Almost every Python "singleton" case. Especially config, loggers, and connection pools.

Design B — pass the object in (dependency injection)

class Printer: ...

def make_app():
    printer = Printer()
    return App(printer=printer)

Callers get the printer from the app.

  • Good: No global state. Every test gets a fresh Printer. Easy to swap for a fake.
  • Bad: Wordy — every function that needs the printer takes it as an argument. Extra work for simple cases.
  • When it wins: Complex apps that care about test isolation. Any app with more than one "singleton" candidate — passing them in keeps them from becoming a tangled web.

Design C — classic Singleton via __new__

class Printer:
    _instance = None
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

Now Printer() always returns the SAME object.

  • Good: Guarantees only one instance, no matter how the caller writes the code. You literally can't build a second one.
  • Bad: Hard to reset between tests (you have to manually clear _instance). Subtle bugs when __init__ runs every time you "construct" and overwrites state on the existing object. Inheritance is fragile.
  • When it wins: When the problem requires only one AND you actually want the "can't build a second one" guarantee (not just Python's "please don't"). Rarely the right answer.

Design D — classmethod instance()

class Printer:
    _instance = None

    @classmethod
    def instance(cls):
        if cls._instance is None:
            cls._instance = cls()
        return cls._instance

Callers use Printer.instance() instead of Printer().

  • Good: Same "only one" behavior as C, but Printer() is still free for tests that want a fresh object. Easy to reset: Printer._instance = None.
  • Bad: Two ways to build (Printer() and Printer.instance()) — the team has to agree which one to use.
  • When it wins: You want singleton-in-production + fresh-instances-in-tests. Best of both worlds if the team is disciplined.

The locked-in choice for THIS problem

For the "one physical printer" scenario:

  • Real requirement, not just convenient → Singleton is on the table.
  • Never two printers, even in tests (tests use a fake printer, not a second real one) → a strong guarantee is worth it.
  • Team is small → the "always use .instance()" convention will be followed.

We pick Design D — classmethod instance().

We rule out:

  • A (module-level) — no guarantee against someone building a fresh Printer. For a real requirement we want the class itself to prevent it.
  • B (pass it in) — the printer is used from many unrelated modules; passing it around everywhere is heavy for this case.
  • C (__new__) — we still want tests to be able to build fresh instances. .instance() gives us both.

Notice the shape of the argument: we started at "clearly Singleton" and ended at "specifically the classmethod version, not the __new__ version". Even within one pattern, requirements decide between the variants.

For a config object or a logger, we would have picked A. Different requirements → different answer.

Mechanics for Design D

class Printer:
    _instance = None

    def __init__(self):
        self._jobs = []

    @classmethod
    def instance(cls):
        if cls._instance is None:
            cls._instance = cls()
        return cls._instance

    @classmethod
    def _reset(cls):
        """Test-only: clear the singleton so a fresh test
        starts with no jobs."""
        cls._instance = None

    def submit(self, job):
        self._jobs.append(job)
        return f"queued {job} (position {len(self._jobs)})"

    def jobs(self):
        return tuple(self._jobs)

Callers use Printer.instance().submit(...).

Your task

Build in main.py:

  1. Printer class:
    • _instance class attribute (starts as None)
    • __init__ initializes self._jobs = []
    • instance() classmethod — returns THE singleton instance, creating it if needed
    • _reset() classmethod — sets _instance = None (for test isolation)
    • submit(self, job) returns f"queued {job} (position {N})" where N is 1-based
    • jobs(self) returns a tuple of submitted job names

Structural tests enforce:

  • Printer.instance() called twice returns the SAME object.
  • Jobs submitted in one call site are visible from another (proves shared state).
  • _reset() gives fresh instances afterward.

Click Submit to run the tests.

Starter files

Preview only — open the lab to edit and run.

files_dir
04-singleton-starter