Software Design

Swift Monostate Pattern: Could It Be an Alternative to the Singleton?

For anyone who has wondered whether using a Singleton really is okay

4 min read
Cover image for Swift Monostate Pattern: Could It Be an Alternative to the Singleton?

For anyone who has wondered whether using a Singleton really is okay

If you develop for iOS, you have probably used a Singleton at least once. I have, too. SomeManager.shared We had code like this in almost every project, usually several times over.

But at some point, this .shared started to become uncomfortable. State got tangled during testing, and team members accessed it freely from all over the codebase.

That is when I discovered the Monostate pattern. Today, I want to share my hands-on experience with what Monostate means in Swift and whether it can really serve as an alternative to Singleton.

To give you the conclusion first, Monostate is a pattern that lets you share one state while creating multiple instances. Its goal is the same as Singleton’s, but the approach is the exact opposite. It is not a universal solution, though; the right choice depends on the situation. Let us unpack it step by step below.

What exactly is the Monostate pattern?

The key idea of Singleton is to create exactly one instance. You block the initializer and use only shared.

Monostate is the opposite. You can create as many instances as you want. Instead, all the state (data) inside them is shared through static properties.

In other words, whether you create object A or B, both look at the same data. They look like ordinary objects from the outside, but are connected to a single shared state internally.

In Swift, it looks roughly like this.

struct Settings {
    private static var _volume = 50   // share the state static
    var volume: Int {                 // use it like an instance
        get { Settings._volume }
        set { Settings._volume = newValue }
    }
}
// different instances, but one shared state
var a = Settings(); a.volume = 80
print(Settings().volume)  // 80

Even if you create a new Settings(), volume is still 80. There are multiple instances but one state—that is Monostate in a nutshell.

There are three instances, but they all observe one state
There are three instances, but they all observe one state

How is it different from Singleton?

The biggest difference is the code that uses it.

With Singleton, callers always have to be aware of .shared. They have to think, “Right, this is a Singleton.” With Monostate, you can simply create Settings() and use it like an ordinary object, so callers do not need to know about the internal implementation.

Here is a summary table.

Category Singleton Monostate
Number of instances Exactly one Multiple allowed
What is shared The instance itself State (static data)
Caller code Explicitly specifies .shared Like an ordinary object
Inheritance Awkward Relatively flexible

Inheritance is particularly interesting. Inheriting from a Singleton is awkward, while Monostate is an ordinary type, so it tends to be more flexible to extend.


So, can it be an alternative to Singleton?

Honestly, sometimes it can, and sometimes it cannot.

Let us start with the advantages. The caller code becomes cleaner, and .shared is not scattered throughout the codebase as a global access point. Since the interface is identical to that of an ordinary object, changing the structure later is less burdensome.

Monostate is a pattern that hides global state. It looks convenient, but hidden global state is a double-edged sword in its own right.

Here is the problem. Because it looks like an ordinary object, a team member may create one thinking, “This is just a local object,” only to discover that its state is shared globally. That can be even more confusing.

And because the state is shared statically, you still have to worry about concurrency issues in multithreaded environments. In that respect, it is no different from Singleton.

In my experience, with modern Swift, I recommend considering dependency injection first rather than Monostate. It makes testing easier and dependencies clearer.

For modern Swift projects, I reach for DI first
For modern Swift projects, I reach for DI first

So when is it a good fit?

In summary, these are the cases.

  1. When you want to gradually eliminate an existing codebase with too many Singletons
  2. When you want to keep the caller-facing interface clean and object-like
  3. When you need to handle global state that requires inheritance or extension

Conversely, if you are starting a new project from scratch, I would look at dependency injection or explicit state management before Monostate.

I keep patterns in my toolbox and take them out when the situation calls for them
I keep patterns in my toolbox and take them out when the situation calls for them

In the end, Monostate is not a “perfect superset of Singleton,” but “another option with a different character.” Keep it in your toolbox and use it when the situation fits.

I hope today’s article offers a small hint to anyone tired of .shared being overused. Rather than switching blindly, take a moment to consider what fits your project best.