go 60 lines · 7 steps

A thread-safe config singleton in Go

Load application configuration from the environment exactly once, safely, using sync.Once.

Explained by highlit
1package config
2 
3import (
4 "fmt"
5 "os"
6 "strconv"
7 "sync"
8 "time"
9)
10 
11type Config struct {
12 DatabaseURL string
13 HTTPPort int
14 RequestTimeout time.Duration
15 Debug bool
16}
17 
18var (
19 instance *Config
20 once sync.Once
21 loadErr error
22)
23 
24func Get() (*Config, error) {
25 once.Do(func() {
26 instance, loadErr = load()
27 })
28 return instance, loadErr
29}
30 
31func load() (*Config, error) {
32 dbURL := os.Getenv("DATABASE_URL")
33 if dbURL == "" {
34 return nil, fmt.Errorf("DATABASE_URL is required")
35 }
36 
37 port, err := strconv.Atoi(getEnv("HTTP_PORT", "8080"))
38 if err != nil {
39 return nil, fmt.Errorf("invalid HTTP_PORT: %w", err)
40 }
41 
42 timeout, err := time.ParseDuration(getEnv("REQUEST_TIMEOUT", "30s"))
43 if err != nil {
44 return nil, fmt.Errorf("invalid REQUEST_TIMEOUT: %w", err)
45 }
46 
47 return &Config{
48 DatabaseURL: dbURL,
49 HTTPPort: port,
50 RequestTimeout: timeout,
51 Debug: getEnv("DEBUG", "false") == "true",
52 }, nil
53}
54 
55func getEnv(key, fallback string) string {
56 if v, ok := os.LookupEnv(key); ok {
57 return v
58 }
59 return fallback
60}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1sync.Once guarantees initialization runs exactly once even under concurrent access.
  2. 2Caching both the result and its error lets every caller see the same outcome without re-running the work.
  3. 3Validating and parsing environment variables at load time turns config mistakes into clear startup errors.

Related explainers

Share this explainer

Here's the card — post it anywhere.

A thread-safe config singleton in Go — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code