Interview questions

Python Singleton Pattern Interview: Singleton vs Module-Level Object vs Dependency Injection

July 30, 2025Updated May 10, 202617 min read
Can The Python Singleton Pattern Be Your Secret Weapon For Acing Technical Interviews

Master the Python singleton pattern interview by comparing singleton, module-level object, and dependency injection, plus when each choice fails.

Most candidates who stumble on the Python singleton pattern interview question know what a singleton is. The problem surfaces one question later, when the interviewer asks: "Why not just use a module-level object?" — and the candidate has no answer because they prepared a definition, not a decision.

This article is about that decision. A Python singleton pattern interview question is really asking you to navigate a three-way choice: singleton, module-level object, or dependency injection. Each has a legitimate use case, and each has a failure mode. Knowing which belongs where — and being able to say why out loud — is what separates a strong answer from a memorized one.

The Singleton Question Interviewers Are Really Asking

Why This Is Really a Judgment Test, Not a Definition Test

An interviewer who asks about singleton already knows the definition. They're not testing recall. They're watching to see whether you treat a pattern as a tool or as a rule — whether you can look at a design choice and ask "is this the right default here, or am I just reaching for something familiar?"

In Python specifically, singleton is a pattern that arrived from Java and C++ contexts where module-level state doesn't exist the same way. The interviewer who knows Python well is often hoping you'll notice that, not because they want you to dismiss singleton, but because recognizing the mismatch is exactly the kind of judgment that distinguishes a mid-level from a senior engineer.

According to Google's Python Style Guide, module-level constants and objects are an explicit, readable way to share state — a point that makes the singleton pattern's ceremony feel less necessary in Python than in languages without equivalent module mechanics.

What a 30-Second Answer Should Actually Cover

A strong 30-second answer has three moves: define the pattern, name the Python alternatives, and state a preference. Something like: "Singleton ensures a class has exactly one instance, but in Python, a module-level object usually achieves the same thing with less overhead and fewer testing surprises. I'd reach for dependency injection when I need to swap or mock the dependency cleanly." That's it. You've shown you know the pattern, you know the language, and you know when the pattern is the wrong tool.

What a Weak Answer Gives Away Immediately

The weak answer recites the definition — "it's a class with a private constructor that returns the same instance every time" — and stops there. It doesn't mention global state, doesn't acknowledge that Python's import system already caches module objects, and doesn't say anything about what happens when you try to test code that relies on one. Interviewers who've run engineering panels report that candidates who give only the textbook definition tend to struggle on every follow-up, because the follow-ups are all about tradeoffs, and the candidate prepared for a vocabulary quiz.

Singleton vs Module-Level Object vs Dependency Injection

The Decision Matrix Most Articles Skip

The three options differ across four dimensions that matter in real code: state ownership, testability, import behavior, and thread safety. Running through them concretely makes the singleton vs module-level object decision much clearer than any slogan.

State ownership. A singleton class owns its state explicitly through the class itself. A module-level object owns its state through the module namespace. A dependency-injected object owns its state wherever it's constructed — and the caller decides where that is. The more control you need over where state lives and who can see it, the more you want DI.

Testability. A singleton is the hardest to test cleanly. Its instance persists across test cases unless you actively reset it. A module-level object is easier to patch with `unittest.mock.patch`, but you're still patching a global. An injected dependency is the easiest — pass a fake, and the test is isolated by construction.

Import behavior. Python caches module imports in `sys.modules`. The first time a module is imported, its top-level code runs; every subsequent import returns the cached module object. This means a module-level object is effectively a shared instance without any class machinery.

Thread safety. A module-level object created at import time is safe in most cases because import itself is protected by a lock in CPython. A singleton created lazily on first access is not automatically safe, and you have to add locking yourself.

Why Module Import Caching Looks Like Singleton Behavior

When you write `import config` in two different files, Python doesn't create two `config` modules. It creates one and hands both files the same reference. Any object you define at the top level of `config.py` — a settings dict, a logger instance, a database URL — is shared across the entire process without a single line of singleton machinery.

This is the Python-specific confusion the singleton vs module-level object comparison has to address. Candidates who learned design patterns from a Java textbook often reach for a singleton class because that's how you get shared state in Java. In Python, the module system already solved that problem. The question is whether the class-based singleton adds anything — and usually the answer is no.

When Dependency Injection Is the Boring Answer That Wins

Dependency injection in Python wins when three conditions appear together: the object has collaborators that need to be swapped in tests, the object's lifecycle shouldn't be tied to import time, and you want callers to be explicit about what they depend on. A database client, an HTTP session, a feature flag provider — these are all objects that should be passed in, not grabbed from a global.

The Python documentation on `unittest.mock` shows how much ceremony is required to patch a global dependency compared to simply injecting a mock. When you find yourself writing `with patch('mymodule.client') as mock_client:` in every test, that's the code telling you it wants DI.

A Python Singleton With `__new__` Still Has Sharp Edges

How `__new__` Creates One Object and `__init__` Still Tries to Run

The classic Python singleton pattern uses `__new__` to control instance creation:

The subtle problem: `__new__` returns the same object every time, but Python still calls `__init__` on whatever `__new__` returns. So if you write `Config("staging")` after the instance already exists, `__init__` runs again and overwrites `self.env`. The singleton controls object identity, not initialization. Candidates who don't notice this gap tend to discover it in production, not in the interview.

What the Same Config or Logger Example Looks Like in Code

A module-level version of the same config object looks like this:

Any file that does `from config import config` gets the same object. No `__new__`, no class variable, no guard logic. The Python singleton pattern adds machinery without adding behavior here — and that machinery is what makes the next section's problems possible.

The Hidden Cost Is Not the Code, It's the State

The real problem with a class-based singleton isn't the implementation complexity. It's that global state is now hidden behind a class interface that looks like it could have multiple instances but doesn't. When the app grows — when you add a second environment, a test harness, or a concurrent worker — the assumption of exactly one instance starts to crack. And because the state is hidden inside a class variable, it's harder to see, harder to audit, and harder to reason about than a module-level object that sits in plain sight.

Thread Safety Is Where the Simple Singleton Story Breaks

Why Double-Checked Locking Is Not a Magic Spell

The instinct when you hear "singleton" and "threads" is to add a lock around the instance creation check:

Double-checked locking works in Python today under the GIL, but it's easy to get wrong. The outer check without the lock is a race condition on platforms or runtimes where object assignment isn't atomic. The pattern requires you to know exactly what memory model you're operating under — and most engineers reaching for it don't.

What Changes in Free-Threaded Python

Python 3.13 introduced experimental support for free-threaded execution — the GIL is no longer guaranteed to be held. The Python 3.13 release notes describe this as an opt-in for now, but the direction is clear. Code that relied on the GIL to make lazy singleton initialization "safe enough" is going to break in ways that are hard to reproduce and harder to debug. If your singleton is created at module import time, you're mostly fine. If it's created lazily, you need real locking discipline.

When Locking Is Worth the Complexity

There is a narrow, legitimate case: a singleton that wraps an expensive shared resource — a connection pool, a hardware interface, a process-wide cache — where lazy initialization matters and concurrent access is expected. In that case, a lock is not optional, and the discipline required to implement it correctly is the cost of the design. The question to ask before going there is whether the resource truly can't be initialized at import time, and whether DI wouldn't serve the same goal with less ceremony.

Testing Singleton-Heavy Code Turns Into State Cleanup

Why Tests Fail for Reasons That Look Random

When a singleton holds state, test order becomes an invisible dependency. Test A sets `Config._instance.env = "staging"`. Test B expects the default. Test B fails. The failure message points at the assertion, not at Test A, and the developer spends an hour looking in the wrong place. This isn't a testing framework problem — it's a structural problem. The singleton is designed to persist, and tests are designed to be isolated. Those two goals are in direct conflict.

How to Mock or Reset Singleton State Without Hating Your Life

The practical escape hatches exist: you can add a `_reset` classmethod that sets `_instance = None`, you can monkeypatch the class variable in a pytest fixture, or you can reload the module between tests. Each of these works. None of them are good. They're workarounds for a design that doesn't want to be tested.

The fact that this fixture has to exist at all is the smell. The pytest documentation on fixtures covers isolation patterns extensively, and none of them recommend managing global class state as a first choice.

Why DI Makes the Test Story Cleaner

Dependency injection in Python makes the test story trivially clean. Instead of patching a global, you pass a fake:

No fixture, no reset, no monkeypatching. The test is isolated by construction. This is why teams that have lived with singleton-heavy code and then moved to DI rarely go back — not because DI is philosophically superior, but because the testing experience is concretely better.

When Singleton Is Acceptable in Python Code

The Narrow Cases Where One Instance Really Is the Point

Singleton earns its place when the constraint "exactly one" is a real domain requirement, not just a convenience. A process-wide logging handler that writes to a single file descriptor. A license manager that must enforce a single activation. A hardware interface where two instances would cause physical conflicts. In these cases, the singleton isn't hiding global state — it's encoding a real-world constraint that the design should make visible.

Why a Module-Level Object Is Often the Cleaner Answer

Even in the legitimate cases, a module-level object usually covers the requirement. A module-level logger instance, a module-level config object, a module-level cache — these are shared, they're initialized once, and they're visible in plain sight without any class machinery. The module-level object gives you the same one-instance behavior with less indirection and fewer surprises. The singleton class adds ceremony that only pays off if you need to subclass the singleton or enforce the constraint across multiple modules that don't share an import chain.

A Production Example Where We Chose DI Instead

A common pattern in backend services: a team builds a config client as a singleton to share across request handlers. It works fine until they need to run integration tests against a staging environment. The singleton is initialized with production credentials at import time, and there's no clean way to swap it. The fix — switching to an injected config client passed through the request context — takes a day to implement and eliminates an entire category of test failures. The singleton wasn't wrong in principle; it was wrong because the team needed to swap the dependency, and singletons don't swap cleanly. Engineering blogs like Martin Fowler's writing on dependency injection describe exactly this failure mode and why DI was designed to solve it.

The Strongest Interview Answer Is a Verdict, Not a Sermon

The Answer to Say Out Loud

When a Python singleton interview question comes up, the answer that lands well sounds something like this: "Singleton ensures exactly one instance of a class exists. In Python, I usually prefer a module-level object because the import system already caches modules, so I get the same shared-state behavior without the class machinery or testing overhead. If I need to swap or mock the dependency, I'd use dependency injection instead. I'd only reach for a true singleton if the 'exactly one' constraint is a real domain requirement I need to enforce explicitly."

That answer defines the pattern, names both alternatives, states a preference, and gives a condition for when the pattern is actually justified. It takes about 20 seconds to say and survives every follow-up.

The Follow-Up Questions You Should Be Ready For

  • "How does import caching work?" Python caches modules in `sys.modules` after the first import. Subsequent imports return the cached module object, so any module-level object is effectively shared.
  • "How would you test a singleton?" Honestly: with difficulty. You'd add a reset hook or monkeypatch the class variable. Then you'd explain why DI would have been the better choice.
  • "What about thread safety?" Lazy initialization needs explicit locking. Import-time initialization is generally safe under CPython's import lock, but free-threaded Python changes the calculus.
  • "Can you show me the code?" Walk through the `__new__` implementation, point out the `__init__` re-run problem, and show the module-level alternative side by side.

The Default Recommendation That Survives Code Review

The verdict that holds up in code review and in interviews: prefer module-level objects for simple shared state, prefer dependency injection when the object has collaborators or needs to be swapped, and reach for a class-based singleton only when you can articulate why the domain requires enforcing exactly one instance at the class level. That's not a rejection of the pattern — it's a precise account of when the pattern earns its complexity.

How Verve AI Can Help You Ace Your Coding Interview With Python Design Patterns

The hardest part of a technical interview isn't knowing the answer — it's knowing what the interviewer is actually testing and being able to articulate the tradeoff clearly under pressure. That's a live performance skill, and it degrades fast when you're nervous and the follow-up diverges from your script. Verve AI Coding Copilot is built for exactly that gap: it reads your screen in real time, sees the problem you're working on, and surfaces the decision language — tradeoffs, alternatives, edge cases — that turns a definition into a verdict. For design pattern questions like singleton, where the real test is whether you can navigate three options and justify a recommendation, Verve AI Coding Copilot gives you the structural framing while you focus on delivery. It works across LeetCode, HackerRank, CodeSignal, and live technical rounds, and the Secondary Copilot mode keeps you on one problem without context-switching. When the interviewer asks "why not just use a module object?" and you need to answer in real time, Verve AI Coding Copilot suggests answers live so the tradeoff comes out clean instead of improvised.

FAQ

Q: What is the singleton pattern in Python in one interview-ready sentence?

The singleton pattern ensures a class has exactly one instance by controlling object creation in `__new__` and storing the instance as a class variable — but in Python, a module-level object usually achieves the same shared-state goal with less ceremony and fewer testing surprises.

Q: How do you implement a singleton in Python, and what role do `__new__` and `__init__` play?

`__new__` controls whether a new object is created: if `_instance` is already set, it returns the existing one. `__init__` still runs on the returned object, which means it can overwrite attributes if you're not careful. Guard `__init__` with a flag or avoid mutable initialization logic entirely.

Q: Why do interviewers often push back on singleton in Python, and what should you say in response?

They push back because Python's module system already provides shared-instance behavior through import caching, making the class-based singleton feel like unnecessary machinery. The right response is to agree — acknowledge that module-level objects are often the cleaner default, and explain that you'd use a singleton only when the domain genuinely requires enforcing exactly one instance at the class level.

Q: When is a singleton actually appropriate, and when should you use a module-level instance instead?

Use a singleton when the "exactly one" constraint is a real domain requirement — a hardware interface, a license manager, a process-wide coordinator with explicit lifecycle needs. Use a module-level instance when you just want shared state across the codebase; the import cache handles the rest without any class machinery.

Q: How does thread safety affect a Python singleton implementation?

A singleton initialized at module import time is generally safe because CPython's import system holds an import lock. A lazily initialized singleton is not automatically safe — you need explicit locking, and double-checked locking requires careful implementation. Free-threaded Python (3.13+) removes the GIL as a safety net, making correct locking discipline mandatory rather than optional.

Q: What are the main testing and maintainability downsides of singleton-heavy code?

Singleton state persists across test cases unless you actively reset it, which creates order-dependent test failures that are hard to diagnose. Maintenance gets harder as the app grows because the global state is hidden inside a class interface rather than visible in the module namespace. Every workaround — reset hooks, monkeypatching, module reloading — is a symptom of the structural problem.

Q: How would you explain the tradeoff between singleton, module import caching, and dependency injection?

Singleton enforces one instance explicitly through class machinery. Module import caching gives you one shared object implicitly through Python's `sys.modules` cache — simpler, more Pythonic, and easier to patch. Dependency injection makes the dependency explicit at the call site, which costs a little wiring but pays off immediately in testability and flexibility. The right choice depends on whether you need explicit enforcement, implicit sharing, or explicit swappability.

Q: What is a strong answer if the interviewer asks for alternatives to the singleton pattern?

Name two: module-level objects for simple shared state, and dependency injection for objects with collaborators or swap requirements. Explain that module-level objects use Python's import cache to share a single instance without class machinery, and that DI lets you pass a fake in tests without touching global state. End with a clear preference — module-level objects for most cases, DI when testability matters — rather than presenting all three options as equally valid.

Conclusion

You walked into this question knowing what a singleton is. The pressure was never about the definition — it was about what comes next, when the interviewer asks why you'd use a class with `__new__` machinery when Python's import system already handles shared instances for free.

Now you have a verdict instead of a pattern tour. Lead with the tradeoff matrix: singleton for explicit one-instance enforcement, module-level objects for simple shared state, dependency injection when you need to swap or mock cleanly. Default to module-level objects or DI. Reach for a class-based singleton only when you can name the domain constraint that makes exactly one instance a real requirement — not just a convenience.

That's the answer that survives the follow-up. Say it plainly, without hedging, and the interviewer will know you've thought about this in production code, not just in a textbook.

DS

Drew Sullivan

Interview Guidance

Ace your live interviews with AI support!

Get Started For Free

Available on Mac, Windows and iPhone