Skip to content
wannacode

Singleton and Dependency Injection

In short: Singleton guarantees a single instance of a class, while Dependency Injection passes dependencies in from outside, so the code can be tested with a substitute, such as a clock.

Problem

The test that passes only on Fridays

On Fridays the shop gives a 10% discount. The program has one clock, so it is a Singleton: a class with only one object, reachable from anywhere. The order service asks it what day it is:

class SystemClock:
def today(self):
return date.today()
clock = SystemClock()
class OrderService:
def price(self, amount):
if clock.today().weekday() == FRIDAY:
return amount * 0.9
return amount

Someone writes a test to make sure the discount works:

service = OrderService()
print(f"Friday discount: expected €90.00, got €{service.price(100):.2f}")

Run on Monday, October 26, 2026, the test prints:

Output
Friday discount: expected €90.00, got €100.00

On a Friday the same test prints 90.00. The test passes one day a week and fails on the other six, and the code is the same. To check the discount you have to wait for Friday or move the computer's clock.

The clock itself is done right. The problem is that price reaches for it itself. From outside you can't see that the service depends on a clock: OrderService() asks for nothing. The test can't give it another day, because it can't give it anything.

The idea

The one object and handing it in

A Singleton guarantees two things: there is only one object, and anyone can take it from anywhere. The first is sometimes needed. The second is the price:

  • a hidden dependency. Who uses the clock shows only if you read every line of every method;
  • shared state. Whatever one piece of code changes in the object, everyone else sees, including the next test;
  • it can't be swapped. The code points to the concrete class by name. Another clock for the test has no way in.

The fix is not to remove the clock, but to hand it in from outside. The service doesn't look for its dependency, it receives it in the constructor. Which clock it is, is decided by whoever creates the service: the program hands in the real one, and the test its own, with a date the test picks.

This is called Dependency Injection (DI). There are three roles:

  • the service says what it needs: a parameter in the constructor. What the object is doesn't matter, as long as it has a today() method;
  • the dependency is the object it is given: the real clock or the test one;
  • the wiring happens in one place, at the start of the program. That is where you decide which object goes where.

The Singleton can even stay: the one clock is handed in like any other object. Nobody just reaches for it on their own.

See it

Singleton, the way the language gives it

In the lesson "Design patterns" you saw a Singleton as a class that keeps its one object. In Python it is rarely done that way, because the language already has something that runs only once: the module.A module (the file clock.py) runs once, on the first import. Every later from clock import clock gives the same object. An object at the top level of a module is a Singleton without a single extra line of code:
class SystemClock:
def __init__(self):
print("Creating the clock")
def today(self):
return date.today()
clock = SystemClock()
print("Start")
Output
Creating the clock
Start
The object is created when the module runs, before the first line that uses it. In this program the module and the code are in one file. In a real project the clock is in its own file, and however many places import it, there is one object.
More precisely, if you are curious
The __new__ trick from "Design patterns" is rarely needed. If you do make a class that keeps one object, remember that __init__ is called on every creation, even when __new__ returns the old object.

See it

The clock comes in from outside

The service asks for a clock in its constructor. What it is doesn't matter, as long as it has a today() method. The module with clock = SystemClock() stays the same:
class OrderService:
def __init__(self, clock):
self.clock = clock
def price(self, amount):
if self.clock.today().weekday() == FRIDAY:
return amount * 0.9
return amount

In the program, the real clock is handed in once, at creation:

service = OrderService(clock)

Now OrderService(...) says out loud that it can't work without a clock. The dependency shows in the constructor's signature, not somewhere inside a method.

See it

The test picks the day itself

The test needs a clock you can tell which date to return:

class FakeClock:
def __init__(self, day):
self.day = day
def today(self):
return self.day

The test hands it in instead of the real one:

clock = FakeClock(date(2026, 10, 30))
service = OrderService(clock)
print(f"Friday: €{service.price(100):.2f}")
clock.day = date(2026, 10, 27)
print(f"Tuesday: €{service.price(100):.2f}")
Output
Friday: €90.00
Tuesday: €100.00

clock = FakeClock(date(2026, 10, 30))
service = OrderService(clock)
print(f"Friday: €{service.price(100):.2f}")
clock.day = date(2026, 10, 27)
print(f"Tuesday: €{service.price(100):.2f}")
Step by step, in words
  1. Before, price reached for the module-level clock itself: a hidden link to a global object the test cannot change.
  2. The test makes its own clock: a FakeClock that says 2026-10-30, a Friday.
  3. The clock comes in from outside, through the constructor. OrderService only keeps a reference to it.
  4. price(100): the service asks its clock. It's a Friday, so the sum is 90.00.
  5. The test moves the date to 2026-10-27, a Tuesday. Nothing in OrderService changes.
  6. The same price(100): a Tuesday, 100.00. The test gives these two numbers on any day of the week.

The test gives these two numbers on a Monday, on a Friday and on a Sunday. OrderService is the same class that runs in the shop: the test changes only what it hands in.

More precisely, if you are curious
A clock with one method can also be a function. The service takes today and calls self.today(). The program hands in date.today, without parentheses, and the test lambda: date(2026, 10, 30). A function is a dependency like any object.

Try it

Predict the output

The classes are from the cards before this one. October 31, 2026 is a Saturday. What does the program print?

clock = FakeClock(date(2026, 10, 30))
shop = OrderService(clock)
clock.day = date(2026, 10, 31)
app = OrderService(clock)
print(f"€{shop.price(50):.2f}")
print(f"€{app.price(50):.2f}")
Answer

Try it

Find the bug

The cart is a Singleton, "so that there is one":

class Cart:
def __init__(self):
self.items = []
def add(self, item):
self.items.append(item)
cart = Cart()

Two tests, each with its own checkout page. check prints OK or what the test expected:

first = CheckoutPage()
first.add("Coffee")
first.add("Mug")
check("Test 1", first.count(), 2)
second = CheckoutPage()
second.add("Coffee")
check("Test 2", second.count(), 1)
Output
Test 1: OK
Test 2: FAIL, expected 1, got 3

Each test passes on its own. Together, the second fails. Which line has the bug?

Pick the line with the bug.

More precisely, if you are curious

The same leaking cart shows up even without a Singleton, from a list as a default value. items=[] runs once, when Python reads the def, and every CheckoutPage() gets the same list:

class CheckoutPage:
def __init__(self, items=[]):
self.items = items
def add(self, item):
self.items.append(item)
def count(self):
return len(self.items)
first = CheckoutPage()
first.add("Coffee")
first.add("Mug")
check("Test 1", first.count(), 2)
second = CheckoutPage()
second.add("Coffee")
check("Test 2", second.count(), 1)
Output
Test 1: OK
Test 2: FAIL, expected 1, got 3

The fix is items=None and self.items = [] if items is None else items.

The idea

When you don't need it

A Singleton makes sense when a second copy would be a mistake and the object keeps nothing that changes: settings read once at startup, the log, the pool of database connections. Even then it is better handed in from outside than reached for by everyone.

Handing in from outside has a price too. It's not worth it when:

  • the dependency never changes and doesn't get in the way of tests. round is not a dependency you hand in;
  • the program is small. Wiring by hand in the start of the program is enough; a DI framework is for big applications;
  • the constructor asks for eight dependencies. That's not a DI problem: the class does too much.

Exercise

Black Friday

Solve the task in your editor.

Black Friday. On November 27, 2026 the shop gives a 30% discount, and on other Fridays still 10%. Add the rule to OrderService and check it with FakeClock for three dates: Black Friday, October 30 (a Friday) and October 27 (a Tuesday). The test gives the same output on any day.

Output
Black Friday: €70.00
Friday: €90.00
Tuesday: €100.00

More tasks

  • Order numbers. The counter for order numbers is a Singleton and starts at 1001. Make it a dependency of OrderService and write two tests that both get 1001 as the first number.
  • A cart for every customer. Fix the bug from "Find the bug" all the way: the cart is not a Singleton, and every page gets its own. Two customers add different items, and each sees only their own.
  • Night discount. Between 22:00 and 6:00 the discount is 5%. The clock now has to give the hour too. What do you change in the clock, and how many tests do you need to be sure?

Test

Check yourself

1. Why does the test for the Friday discount pass only on Fridays?
2. What does handing in the dependency (Dependency Injection) change?
3. Two tests use the same cart, a Singleton. The first adds two items, the second adds one and expects 1. What happens?
4. When is a Singleton in place?

Made by Ivo Gergov as part of Evo Tech's campaign for accessible programming education ·