Rule of thumb

If behaviour is stateless — no config that varies per instance, no dependencies to inject — package-level variables and functions are cleaner than a struct + constructor.

Use constructorUse package-level
Config varies per instanceConfig is fixed / constant
Fields change over lifetimeBehaviour is stateless
Injecting dependencies (e.g. DB for tests)No dependencies to inject
Implementing an interface for swappabilityA plain function is the whole API

Example: location name sanitiser (Beep)

Regex patterns for stripping prefixes/suffixes like [Upcoming] never change at runtime.

// ❌ Before — unnecessary constructor
sanitiser := location_name_sanitiser.NewLocationNameSanitiser()
result := sanitiser.Sanitise(name)
 
// ✅ After — compiled once on import, zero boilerplate
var prefixPatterns = compilePrefixPatterns()
var suffixPatterns = compileSuffixPatterns()
 
func Sanitise(name string) SanitisationResult { ... }
 
result := location_name_sanitiser.Sanitise(name)

var at package scope initializes once on first import — as safe and efficient as sync.Once, with none of the ceremony.