The Golang Masterclass: Singleton Structs Will Save Your Project.

At some point, your project outgrows the usual patterns. It starts to split into mini-projects, not big enough for separate libraries (which are a pain to manage), but too isolated to share logic cleanly.

But the real headache begins: when two or more of these mini-projects need the same behavior. Which is a full-blown object with state.

The answer? Singleton structs, objects you create once and reuse across your app, with state and all.

Hoist it. Literally. It’ll save you from half the complexity.

I hit this exact problem while working on my agentic project. The modelservice embeds a tiny utility server as a webhook for a browser extension. Until I embedded a frontend using WebView, which also needs a server to serve static assets (because, of course, paths are hell).

Project layout:

modelservice   // handles agentic calls  
frontend       // webview UI  
maskot         // OpenGL mascot renderer  
observer       // native + web observation tools  
utils/systray  // system tray stuff

Now the question:
How do you start a server in these isolated, self-contained modules? Start it in modelservice? Sure, but how do you pass the server instance to frontend? Or vice versa?

The fix is simple: hoist the server to a shared singleton.

hoisted server
   ├── modelservice  
   └── frontend

So both modules share a single server instance, and it solves the even nastier problem of: who starts the server, and where do you register routes?

Game devs were onto something, there’s a reason they love singletons.

  • Singletons in Go

    • Task Management Singleton
  • More Advanced Example(webui server utility)

Singletons in Go

You’d be surprised how dead simple Go makes singletons, even as a low-level language.

Let’s build a basic task manager as an example(the pattern is the same even for complex projects).

Task Management Singleton

import (
    "sync"
)

type task struct {
    Id          string `json:"id"`
    Title       string `json:"title"`
    Description string `json:"description"`
}

type TaskManager struct {

    tasks  []task
    mu     sync.Mutex  // for synchronization
}

var (
    TaskInstance *TaskManager 
    once         sync.Once
)

func GetTaskManager() *TaskManager {
    once.Do(func() {
        TaskManInstance = &TaskManager{
        }
    })
    return TaskManInstance
}

func (t *TaskManager) AddTask(task string) error {}
// other methods below 

The trick lives in GetTaskManager. Thanks to once.Do, the TaskManager struct is instantiated exactly once. After that, it just returns the same object, state, logs, tasks, everything included and maintained.

Access it anywhere in your project:

man := GetTaskManager() // Keeps its state across modules

Ridiculously easy.

More Advanced Example(webui server utility)


How to Build a Robust Go Webview UI: with a Utility Server and NavStack

Webview UI that not only loads reliably but also navigates smoothly

favicon
skdev.substack.com

Singletons are a legit pattern to have in your Go toolbox. I’ve used this same approach in C++, and the utility holds across languages.

Whenever you’re unsure how to handle shared state or common behavior, hoist it. Don’t duplicate it. Game devs figured this out long ago for a reason.

But remember with great power comes great responsibility.

I’ll be posting more deep dives on backend topics, Golang, and low-level systems on Substack. Would love to have you there; come say hi:

Coffee & Kernels

Similar Posts