Avoid global state in Go

By Peter Leinonen on July 27, 2025

Global state in Go can feel like a shortcut—but it’s one that leads straight into long-term complexity. Whether you're writing a web service, CLI tool, or package, using global variables makes your code harder to test, maintain, and reason about. The good news? Go's simplicity makes it easy to avoid. Use structs and dependency injection instead.

Why Global State Is a Problem

Global variables introduce hidden dependencies—any part of your code can change them at any time. This leads to:

  • Tight coupling: Code becomes dependent on global configuration.
  • Race conditions: Concurrent access to shared globals causes subtle bugs.
  • Poor testability: Tests must reset or mock globals, which is brittle and messy.

How It Complicates Testing

Suppose you have a global logger or database connection:

var db *sql.DB

func GetUser(id int) (*User, error) {
    return db.QueryRow("SELECT * FROM users WHERE id = ?", id)
}

Now imagine writing a unit test for GetUser. You have to:

  1. Ensure db is initialized.
  2. Possibly mock it—globally.
  3. Avoid breaking other tests that also use db.

This creates test coupling, where tests depend on each other’s state. One test failing to reset the global properly can cause unrelated tests to fail.

A Better Pattern: Structs + Dependency Injection

Encapsulate dependencies in a struct and pass them in:

type Service struct {
    DB *sql.DB
}

func (s *Service) GetUser(id int) (*User, error) {
    return s.DB.QueryRow("SELECT * FROM users WHERE id = ?", id)
}

And then wire it up in main:

func main() {
    db := setupDB()
    service := &Service{DB: db}
    // Use service.GetUser(...)
}

In tests:

func TestGetUser(t *testing.T) {
    mockDB := setupMockDB()
    service := &Service{DB: mockDB}
    // assert service.GetUser(...)
}

Final Thought

Avoiding global state isn’t about purity—it’s about clarity, testability, and safety. Go gives you everything you need to do this well. Use structs to group your dependencies, inject them explicitly, and you’ll write code that’s easier to test, understand, and grow.

Leave global state behind. Your future self (and your test suite) will thank you.