Singleton Design Pattern
Hello everyone! After a long break, I have prepared an article highlighting the points we need to pay attention to regarding commonly used design patterns.
As we all know, design patterns refer to general approaches developed to solve commonly encountered design problems. These patterns can be categorized into three main categories: creational, behavioral, and structural. Creational patterns organize how objects are created, while behavioral patterns manage communication and task distribution among objects. Structural patterns, on the other hand, organize relationships between objects.
Singleton pattern is one of the creational design patterns and ensures that only one instance of a class is created. This is typically used in situations where an object is shareable and easily accessible from anywhere in the application.

But why is the singleton pattern so widely used? In which situations is it more appropriate to use?
Here are some example scenarios where we can use the Singleton pattern in application development:
- Manage the application’s database connection in a centralized manner,
- Make general data accessible throughout the application, such as session state, language preferences, and theme settings,
- Network Operations and API Requests,
- Manage in-app resources (e.g., images, files),
- Application State and Cache Management
Now let’s take a look at how it is used in both Java and Kotlin.
Java
Let’s start with Java first so that when we decompile the Kotlin bytecode, what we see will make more sense.
Implementing the Singleton pattern typically involves creating only one instance of the class and providing global access to this instance. For this purpose, the class needs to have its own unique instance. This instance is usually accessed through a method like getInstance().
Let’s create a Java class as in the example below and create a default constructor inside it. Our goal is to create the object once and use the same instance throughout the application lifecycle. Therefore, to be able to call the getInstance method without creating a new instance of the object, as we all know, we need to make the method static. Since only static variables are allowed inside a static method, we also need to make the instance object static.
private static Singleton instance = new Singleton();
private Singleton(){
System.out.println("Singleton created");
}
public static Singleton getInstance() {
return instance;
}
fun main() {
for (i in 0..9) {
var singleton = Singleton.getInstance()
}
}
When we call the getInstance method of the Singleton class inside the main method, we see “Singleton created” printed to the screen only once. Then, we see numbers from 1 to 10 printed one below the other.
private static Singleton instance = new Singleton();
private static int number = 0;
private Singleton(){
System.out.println("Singleton created");
}
public static Singleton getInstance() {
number++;
System.out.println(number);
return instance;
}
So, what happens when we initialize the instance variable as a top-level new instance within our Singleton class?
Problem 1 :
Here, we observe that our object is immediately created when the class is loaded, without waiting for its usage. This process, called “Eager loading”, entails loading all the variables defined in this way instantly when the class is first created, leading to some performance loss.
Solution:
To prevent this, we initialize the variable only when we need it, at the time of its usage. This approach is known as “Lazy loading”.
Therefore, we should perform the object creation task inside the getInstance method.
private static Singleton instance = null;
private static int number = 0;
private Singleton(){
System.out.println("Singleton created");
}
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
number++;
System.out.println(number);
return instance;
}
Great! But is everything fine this way? I think, no.
Problem 2:
Let’s say we’re working in a multithreaded environment, and the getInstance method is called simultaneously. In that case, both instances will be null, and they may create separate objects!
Solution:
We can solve this significant problem by making the instance creation process safe using synchronized, as shown below.
Awesome!
private static volatile Singleton singleton;
private static int number = 0;
private Singleton(){
System.out.println("Singleton created");
}
public static Singleton getInstance() {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
number++;
System.out.println(number);
return instance;
}
Here, we also use the volatile keyword for the instance object. volatile ensures that the value of the variable is stored in main memory and allows every thread to read this value. When this value in main memory is changed by any thread, it guarantees that other threads directly see this change. This ensures the safe usage of the variable, especially in multi-threaded environments.
Problem 3 :
However, the synchronized method slows down the operation and causes some performance loss. This is because, when working with multiple threads, threads enter a queue and wait for each other’s execution when encountering synchronized.
Solution:
In the code below, to prevent this performance loss, synchronized will only work when the object is null, and it will not work for subsequent instances since the object has already been created. Thus, there will be no performance loss. This approach is called “Double checked locking”.
private static volatile Singleton singleton;
private static int number = 0;
private Singleton() {
System.out.println("Singleton created");
}
public static Singleton getInstance() {
// double-checked locking
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
number++;
System.out.println(number);
return singleton;
}
Great! By creating a singleton class in Java using both lazy loading and thread-safe approaches, we have addressed any issues. We have no remaining problems.
Kotlin
Let’s move on to creating a singleton in our beloved Kotlin language. In Kotlin, we can implement the singleton pattern in 2 different ways: by using the object
keyword or by creating a class with a companion
object.
- Creating a Singleton using the object Keyword:
object SingletonByObject {
init {
println("Singleton created")
}
}
The above code is referred to as Object Declaration. Kotlin documentation mentions that “The initialization of an object declaration is thread-safe and done on first access. Object declarations are initialized lazily, when accessed for the first time.” Great! It seems creating a singleton class in Kotlin that provides both thread safety and lazy loading is a piece of cake!
Let’s see what we find when we decompile the bytecode.
public final class SingletonByObject {
@NotNull
public static final SingletonByObject INSTANCE;
private SingletonByObject() {}
static {
SingletonByObject var0 = new SingletonByObject();
INSTANCE = var0;
String var1 = "Singleton created";
System.out.println(var1);
}
}
Firstly, we notice that it’s converted to “public final class” in bytecode. Final classes cannot be inherited. We observe that its constructor is private. Therefore, it cannot be accessed from outside and cannot be instantiated multiple times. We also see that the instance object is created within the static block. In Java, static blocks are executed at class loading time. This ensures lazy loading and thread safety.
2. Creating a Singleton with Companion
class Singleton private constructor() {
init {
println("Singleton created")
}
companion object {
@Volatile
private var instance: Singleton? = null
fun getInstance() {
instance ?: synchronized(this) {
instance ?: Singleton().also {
instance = it
}
}
}
}
}
Here, there is a difference. If we create a singleton using the object method, we cannot define a constructor. For example, the following code will result in an error.

If we need to access the constructor and provide parameters, as suggested by the IDE, we can convert the object into a class and establish a singleton structure as follows.
class Singleton private constructor(val name: String) {
init {
println("Singleton created")
}
companion object {
@Volatile
private var instance: Singleton? = null
fun getInstance(name: String) {
instance ?: synchronized(this) {
instance ?: Singleton(name = name).also {
instance = it
}
}
}
}
}
Okay, we have successfully created thread-safe and performant singleton classes in both languages. Finally, let’s take a look at the advantages and disadvantages of the Singleton design pattern, which is widely used.
Advantages:
Guaranteed Single Instance: The Singleton pattern guarantees that only one instance of a class is created. This ensures control over a shared object among different components of the application.
Global Access Point: The Singleton instance typically provides a global access point. This makes it easily accessible from anywhere in the application.
Memory Management: The Singleton pattern optimizes memory usage by ensuring that an object is created only once. It prevents unnecessary memory consumption by using a single instance throughout the application.
Great! How about the disadvantages?
Disadvantages:
Flexibility Issues: The Singleton pattern can create tight coupling and high dependency. Using Singleton in your code can make it difficult for the class to be modified by another class or to be inherited by another class.
Testing Challenges: The Singleton pattern can make writing unit tests difficult. Since the Singleton object is shared among the classes being tested, it can prevent isolation of tests.
Concurrency Issues: The Singleton pattern should be used carefully in multi-threaded environments. If the Singleton object is not synchronized properly, unexpected behaviors may arise among multiple threads.
Can Become a Single Point of Dependency: The Singleton pattern can create a significant dependency on a single object accessed from different parts of the application. This can make code maintenance difficult and lead to widespread impacts from changes.
Considering these advantages and disadvantages, it is evident that the Singleton pattern should be used carefully and appropriately. As always, the decision to determine the suitable design pattern will depend on our application requirements and usage scenarios.
Thank you for reading. I hope it has been helpful.