(If possible) Never call regexp.MustCompile (or regexp.Compile) inside a function body that is called frequently. Compile regexes once as package-level var declarations.
Why: regexp.MustCompile is expensive — it allocates and parses on every call. When the function is on a hot path (e.g., called per-label, per-query evaluation), this shows up as measurable CPU and allocation overhead. In one system’s cleanRepeatedWildcards, inlining the compile cost 6× more CPU and 35× more allocations per call vs. package-level pre-compilation.
How to apply: Any time you write a regex in Go, ask: “will this function be called in a loop or on a hot path?” If yes (or even maybe), hoist it to a var at package scope. The only valid reason to compile inline is if the pattern is dynamically constructed from runtime input.
Bad (anti-pattern):
func cleanRepeatedWildcards(s string) string {
re := regexp.MustCompile(`\.\*(\.\*)+`) // compiled on every call
return re.ReplaceAllString(s, ".*")
}Good:
var repeatedWildcardRegex = regexp.MustCompile(`\.\*(\.\*)+`) // compiled once at startup
func cleanRepeatedWildcards(s string) string {
return repeatedWildcardRegex.ReplaceAllString(s, ".*")
}Exception — dynamic patterns can’t be hoisted, but can still be cached:
// Naive: no choice but to compile each call if pattern varies
func matchesUserPattern(s, userInput string) bool {
re := regexp.MustCompile(userInput)
return re.MatchString(s)
}
// Better: cache compiled regexes if the same pattern repeats across calls
var regexCache sync.Map
func matchesUserPattern(s, userInput string) bool {
if v, ok := regexCache.Load(userInput); ok {
return v.(*regexp.Regexp).MatchString(s)
}
re := regexp.MustCompile(userInput)
regexCache.Store(userInput, re)
return re.MatchString(s)
}