Environment Config Go
Fast recall
Quick revision
Core answer
Whether you understand Environment Config Go beyond memorized syntax.
Giving a definition without explaining a practical trade-off.
Connect the concept to a small production example and state its complexity or failure mode.
Interview-ready guide
Interview answer
The pattern (12-factor config):
type Config struct {
Port string
DatabaseURL string
LogLevel string
MaxConns int
}
func LoadConfig() (*Config, error) {
c := &Config{
Port: getenv("PORT", "8080"), // default
DatabaseURL: os.Getenv("DATABASE_URL"), // required
LogLevel: getenv("LOG_LEVEL", "info"),
MaxConns: getint("MAX_CONNS", 25),
}
if c.DatabaseURL == "" {
return nil, fmt.Errorf("DATABASE_URL is required")
}
return c, nil // validated — fail fast
}
func getenv(key, def string) string {
if v := os.Getenv(key); v != "" { return v }
return def
}The principles:
- 1The binary is identical across environments — the ENV VARS are the difference (the 12-factor separation)
- 2Config loads once at startup, validated — fail fast (the service that starts with a missing DB URL never runs half-configured)
- 3Defaults in code (the sane baseline), overrides via env (the deployment's config)
- 4Secrets NOT in code — env vars (or the vault at the highest level)
The sources by environment:
- Dev: .env file (the convenience — os.Setenv via godotenv in the init)
- Docker: -e flags or compose environment
- Kubernetes: configMap (the config) + secret (the passwords) mounted as env
- The orchestrator's env is the production source of truth
The full-featured version: envconfig, viper, or koanf libraries — struct tags (env:"PORT,default=8080"), type conversion, and the validation built into the parse. The hand-rolled version above is the mechanism those libraries wrap.
Environment Config Go
Direct Answer
12-factor: config in env vars, the app reads via os.Getenv/LookupEnv at startup. The same binary everywhere; the env vars differ. Structs with defaults, validation, and fail-fast on missing.
Deep Dive & Architecture
The config-loading lifecycle (the startup discipline): main loads config → validates → constructs dependencies (the DB pool from the DSN) → starts the server. The fail-fast contract: a missing required variable errors at STARTUP (in one place, with a clear message) — never mid-request in a deep goroutine. The anti-pattern the question catches: os.Getenv scattered through the codebase (config read at request time = a config change needs no restart = an inconsistent-config bug).
The typed-config pattern (the next step): parse the env into a typed struct once; pass the struct (or its fields) everywhere. The code below the config layer NEVER touches os.Getenv — the config's single load point is the seam that makes it testable (inject a different Config) and auditable (one place lists every knob).
The secrets layer: env vars are readable via docker inspect / the process environment — for actual secrets (DB passwords, API keys), Kubernetes secrets or a vault beat plain env vars. The 12-factor rule's production refinement: config in env, secrets in the secret manager, mounted as env at container start.
Common Mistakes & Pitfalls
Giving a definition without explaining a practical trade-off.
Check your understanding
Answer these without looking back at the article.
- When would you use Environment Config Go in a real project?
- How would you compare Environment Config Go with an alternative?
- What is a common mistake when using Environment Config Go?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.