Singleton Design Pattern
One of the most commonly used design pattern is Singleton which ensures an entity has one and only one source of truth.
Oct 02, 2025•Fahimul Islam•2 min
Singleton is a creational design pattern, which ensures that only one object of its kind exists and provides a single point of access to it for any other code.
Singleton has almost the same pros and cons as global variables. Although they’re super-handy, they break the modularity of your code.
You can’t just use a class that depends on a Singleton in some other context, without carrying over the Singleton to the other context. Most of the time, this limitation comes up during the creation of unit tests.
Learn more about Singleton
Conceptual Example
Usually, a singleton instance is created when the struct is first initialized. To make this happen, we define the getInstance method on the struct. This method will be responsible for creating and returning the singleton instance. Once created, the same singleton instance will be returned every time the getInstance is called.
How about goroutines? The singleton struct must return the same instance whenever multiple goroutines are trying to access that instance. Because of this, it’s very easy to get the singleton design pattern implemented wrong. The example below illustrates the right way to create a singleton.
Some points worth noting:
- There is a
nil -check at the start for making sure singleInstance is empty first time around. This is to prevent expensive lock operations every time the getinstance method is called. If this check fails, then it means that the singleInstance field is already populated.
- The
singleInstance struct is created within the lock.
- There is another
nil -check after the lock is acquired. This is to ensure that if more than one goroutine bypasses the first check, only one goroutine can create the singleton instance. Otherwise, all goroutines will create their own instances of the singleton struct.
single.go: Singleton
1package main
2
3import (
4 "fmt"
5 "sync"
6)
7
8var lock = &sync.Mutex{}
9
10type single struct {
11}
12
13var singleInstance *single
14
15func getInstance() *single {
16 if singleInstance == nil {
17 lock.Lock()
18 defer lock.Unlock()
19 if singleInstance == nil {
20 fmt.Println("Creating single instance now.")
21 singleInstance = &single{}
22 } else {
23 fmt.Println("Single instance already created.")
24 }
25 } else {
26 fmt.Println("Single instance already created.")
27 }
28
29 return singleInstance
30}
1package main
2
3import (
4 "fmt"
5 "sync"
6)
7
8var lock = &sync.Mutex{}
9
10type single struct {
11}
12
13var singleInstance *single
14
15func getInstance() *single {
16 if singleInstance == nil {
17 lock.Lock()
18 defer lock.Unlock()
19 if singleInstance == nil {
20 fmt.Println("Creating single instance now.")
21 singleInstance = &single{}
22 } else {
23 fmt.Println("Single instance already created.")
24 }
25 } else {
26 fmt.Println("Single instance already created.")
27 }
28
29 return singleInstance
30}
main.go: Client code
1package main
2
3import (
4 "fmt"
5)
6
7func main() {
8
9 for i := 0; i < 30; i++ {
10 go getInstance()
11 }
12
13 // Scanln is similar to Scan, but stops scanning at a newline and
14 // after the final item there must be a newline or EOF.
15 fmt.Scanln()
16}
1package main
2
3import (
4 "fmt"
5)
6
7func main() {
8
9 for i := 0; i < 30; i++ {
10 go getInstance()
11 }
12
13 // Scanln is similar to Scan, but stops scanning at a newline and
14 // after the final item there must be a newline or EOF.
15 fmt.Scanln()
16}
output.txt: Execution result
1Creating single instance now.
2Single instance already created.
3Single instance already created.
4...
5Single instance already created.
1Creating single instance now.
2Single instance already created.
3Single instance already created.
4...
5Single instance already created.
Another Example
There are other methods of creating a singleton instance in Go:
init function
We can create a single instance inside the init function. This is only applicable if the early initialization of the instance is ok. The init function is only called once per file in a package, so we can be sure that only a single instance will be created.
sync.Once
The sync.Once will only perform the operation once. See the code below:
syncOnce.go: Singleton
1package main
2
3import (
4 "fmt"
5 "sync"
6)
7
8var once sync.Once
9
10type single struct {
11}
12
13var singleInstance *single
14
15func getInstance() *single {
16 if singleInstance == nil {
17 once.Do(
18 func() {
19 fmt.Println("Creating single instance now.")
20 singleInstance = &single{}
21 })
22 } else {
23 fmt.Println("Single instance already created.")
24 }
25
26 return singleInstance
27}
1package main
2
3import (
4 "fmt"
5 "sync"
6)
7
8var once sync.Once
9
10type single struct {
11}
12
13var singleInstance *single
14
15func getInstance() *single {
16 if singleInstance == nil {
17 once.Do(
18 func() {
19 fmt.Println("Creating single instance now.")
20 singleInstance = &single{}
21 })
22 } else {
23 fmt.Println("Single instance already created.")
24 }
25
26 return singleInstance
27}
main.go: Client code
1package main
2
3import (
4 "fmt"
5)
6
7func main() {
8
9 for i := 0; i < 30; i++ {
10 go getInstance()
11 }
12
13 // Scanln is similar to Scan, but stops scanning at a newline and
14 // after the final item there must be a newline or EOF.
15 fmt.Scanln()
16}
1package main
2
3import (
4 "fmt"
5)
6
7func main() {
8
9 for i := 0; i < 30; i++ {
10 go getInstance()
11 }
12
13 // Scanln is similar to Scan, but stops scanning at a newline and
14 // after the final item there must be a newline or EOF.
15 fmt.Scanln()
16}
output.txt: Execution result
1Creating single instance now.
2Single instance already created.
3Single instance already created.
1Creating single instance now.
2Single instance already created.
3Single instance already created.
Based on: Golang By Example