skip to content

Regular Expressions

The regexp package is an RE2 engine, so it guarantees linear-time matching and refuses backreferences. Compiling once and reading submatches correctly is the rest of the job.

part ofGo (Golang)overview, primer and where to startread it →
on this pageshow

explore

questions

13

In Go's regexp package, when should you use regexp.MustCompile instead of regexp.Compile?

level: juniorimportance: must knowfreq 72%

answer

  1. who wrote the pattern?
  2. one returns an error, one panics
  3. the stdlib Must convention
  4. literal in source versus config file
  5. package-level var compiles before main

basics

~20 s

regexp.MustCompile panics if the pattern is invalid and returns only a *regexp.Regexp, so it suits patterns written as literals in your source, normally as a package-level var. Use regexp.Compile, which returns an error, for any pattern supplied at runtime.

solid answer

~50 s

`regexp.Compile(expr string) (*regexp.Regexp, error)` is the general constructor: it reports a bad pattern as an error you handle. `regexp.MustCompile(expr string) *regexp.Regexp` is the same thing with the error turned into a panic, following the stdlib `Must` convention. The rule of thumb is where the pattern comes from. If it is a string literal in your code, a failure is a programmer bug, not an input condition, so `MustCompile` is right — and the idiom is a package-level `var re = regexp.MustCompile(...)`, which compiles once during package initialisation, before `main` runs, so a typo blows up at process start rather than on the first request. If the pattern comes from a config file, a CLI flag, a database row or a request, use `Compile` and surface the error to whoever wrote the pattern. Never wrap `MustCompile` in `recover` to fake that.

code

go · 11 lines
go
// Fixed in source: a bad pattern here is a bug, so panic at init.
var idRe = regexp.MustCompile(`^[a-z]+-[0-9]{4}$`)

// Supplied by an operator: a bad pattern is input, so report it.
func compileFilter(pattern string) (*regexp.Regexp, error) {
	re, err := regexp.Compile(pattern)
	if err != nil {
		return nil, fmt.Errorf("invalid filter %q: %w", pattern, err)
	}
	return re, nil
}

go deeper

for a junior

Be ready to state both signatures from memory and say which one panics. Know that the everyday form for a fixed pattern is a package-level var initialised with regexp.MustCompile.

for a middle

Explain that MustCompile is Compile plus a panic, that package-level initialisers run before main, and that compiling parses the pattern into a program every time it is called.

for a senior

Show the judgment: patterns from config, flags or requests are input and must go through Compile with the error reported to the operator. Be able to argue why recovering around MustCompile is the wrong fix.

for a principal

Frame it as a failure-domain decision for the codebase: which pattern sources are allowed to abort the process at start-up, and which must degrade to a rejected reload with the previous rules still serving traffic.

## The two constructors Go's `regexp` package gives you two ways to turn pattern text into a matcher: - `func Compile(expr string) (*Regexp, error)` — returns a compiled `*regexp.Regexp`, or `nil` plus an error describing what is wrong with the pattern. - `func MustCompile(expr string) *Regexp` — the same work, but a bad pattern panics instead of returning an error, so there is only one return value. `MustCompile` is literally `Compile` with the error path converted to a panic. There is no difference at match time: both hand back the same kind of compiled object, and neither is faster than the other once you have it. ## What "compiling" actually does Compiling is real work. The pattern text is parsed into a syntax tree (that is the `regexp/syntax` package), simplified, and then turned into a program the matching engine executes. That cost is paid once per `Compile`/`MustCompile` call, and it is typically far larger than the cost of matching one short string. That is why the whole leaf is about *reusing* the compiled value rather than rebuilding it. ## The choice is about where the pattern comes from The question to ask is: **can this pattern be wrong at runtime in a way that is not my bug?** - A pattern that is a string literal in your source cannot change after you ship. If it fails to parse, your code is broken, and you want to know immediately and loudly. `MustCompile` is correct. - A pattern that arrives from a config file, an environment variable, a command-line flag, an admin UI or a database row is *input*. Someone else can get it wrong, and taking the process down over their typo is a poor design. `Compile` is correct, and the error should be reported with enough context to fix the pattern. ## The package-level var idiom The conventional shape for a fixed pattern is: ```go var idRe = regexp.MustCompile(`^[a-z]+-[0-9]{4}$`) ``` This matters for three reasons: 1. **It compiles exactly once.** Package-level variable initialisers run during package initialisation, before `main`. Every later call reuses the same compiled `*regexp.Regexp`. 2. **Failure happens at start-up.** If the literal is malformed, the panic occurs as the program initialises, not deep inside a request handler an hour later. 3. **It is shareable.** One compiled `*regexp.Regexp` can be used by the whole program for its lifetime; its matching methods are safe to call from many goroutines at once, so you do not need a copy per caller. The same `Must` convention appears elsewhere in the standard library (for example `template.Must`), and it always means the same thing: "this input is baked into the program, so an error here is a bug." ## Handling the error from Compile properly ```go re, err := regexp.Compile(userPattern) if err != nil { return fmt.Errorf("invalid filter %q: %w", userPattern, err) } ``` On error, the returned `*Regexp` is `nil`; there is no partially usable matcher to fall back on. Check the error before touching the value or you will dereference a nil pointer. ## Anti-patterns interviewers listen for - **`MustCompile` on runtime input.** A single bad character in an operator's config now crashes the service. This is the most common real defect in this area. - **`defer recover()` around `MustCompile`** to "handle" a bad pattern. If you need error handling, you needed `Compile`. Recovering here hides the design mistake and leaves the surrounding code with no matcher. - **Compiling in the hot path.** Calling `MustCompile` inside a function that runs per request or per line pays the parse cost every time. Hoist it. - **Believing `MustCompile` is an optimisation.** It is not; the only difference is how a malformed pattern is reported. ## A useful mental split Treat a compiled `*regexp.Regexp` like any other expensive, immutable, reusable value: build it once as far up the lifetime as you can, then read from it many times. `MustCompile` is the version of that for values known when you write the code; `Compile` is the version for values known only when the program runs.

  • Why is a package-level var the usual home for a MustCompile call?
    Package-level initialisers run during package initialisation, before `main`, so the pattern is compiled exactly once and a malformed literal panics at process start instead of inside the first request that reaches it. Every later call then reuses the one compiled `*regexp.Regexp`.
  • A pattern comes from a config file that operators edit and the service hot-reloads. What breaks if you use regexp.MustCompile there?
    A single typo in the config panics the goroutine that reloads it and normally takes the process down, turning an operator mistake into an outage. Use `regexp.Compile`, reject the reload, keep the previously compiled rules in place, and report the error with the offending pattern quoted.
  • If regexp.Compile returns an error, is the returned *regexp.Regexp still usable?
    No — it is `nil`. There is no degraded matcher to fall back on, so you must check the error before using the value, otherwise the first call on it is a nil-pointer dereference.

saying these in an interview costs you the question

  • Claiming MustCompile matches faster than Compile
  • Calling MustCompile on a pattern from config or a request
  • Wrapping MustCompile in recover to handle bad patterns
  • Using the Regexp returned alongside a non-nil Compile error
  • Compiling the same literal pattern inside a per-request function
open as a page

Why does Go's regexp package reject lookaheads and backreferences?

level: juniorimportance: must knowfreq 60%

basics

~20 s

Go's regexp implements RE2 semantics: it simulates an automaton over the input instead of backtracking, so matching time stays bounded by input length times pattern size. Lookaheads and backreferences cannot be expressed in that model, so such patterns fail to compile.

open as a page

In Go's regexp package, what does FindStringSubmatch return for a match and for a non-match?

level: juniorimportance: must knowfreq 55%

basics

~20 s

FindStringSubmatch returns a []string in which element 0 is the entire matched text and elements 1..n are the capture groups, numbered by opening parenthesis. If the pattern matches nowhere in the input it returns nil, so check for nil before indexing.

open as a page

Why is regexp.MatchString(pattern, s) more expensive than calling MatchString on a compiled *regexp.Regexp?

level: middleimportance: should knowfreq 58%

basics

~20 s

The package-level regexp.MatchString compiles its pattern from scratch on every call and throws the compiled matcher away afterwards. The method runs on a *regexp.Regexp that was compiled once, so each call pays only for matching.

open as a page

In Go's regexp package, how do you turn on case-insensitive or dot-matches-newline matching?

level: middleimportance: should knowfreq 45%

basics

~20 s

Put the flag inside the pattern text: (?i) for case-insensitive, (?s) to let . match a newline. Go's regexp has no flags argument, so (?i), (?m), (?s) and (?U) written in the pattern are the only way to set modes.

open as a page

In Go's regexp package, how do you read a named capture group out of a match?

level: middleimportance: should knowfreq 42%

basics

~20 s

Name a group in the pattern with (?P<name>...), then call SubexpIndex("name") to get its slot number and index the match slice with it. SubexpNames returns the names as a slice parallel to the match, with an empty string at slot 0.

open as a page

A Go log-ingest sidecar's CPU profile is dominated by regexp compilation rather than matching. How do you diagnose and fix it?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Patterns are being compiled inside the per-line path instead of once. Find the call — usually regexp.MatchString in the loop or rules compiled per event — compile every pattern once when the ruleset loads, and reuse the shared *regexp.Regexp values for every line.

open as a page

Go's regexp panics on a rule set ported from a PCRE engine at proxy start-up — how do you triage which rules are unportable and rewrite them?

level: seniorimportance: should knowfreq 34%

basics

~20 s

Stop the crash-loop, then compile every rule in a test so failures surface in CI rather than at start-up. Group the parse errors by construct, rewrite lookarounds as two-stage matches in Go, and add traffic fixtures for rules that still compile.

open as a page

A Go tool indexes FindStringSubmatch's result at m[1] and panics mid-rewrite — what went wrong?

level: seniorimportance: should knowfreq 30%

basics

~20 s

A line did not match, so FindStringSubmatch returned nil and indexing it panicked with index out of range, length 0. Guard the result for nil before indexing, and write rewritten files atomically so a panic cannot leave half the tree edited.

open as a page

Go's regexp cannot express your rules' lookaheads — how do you decide between rewriting them and adopting a backtracking regex dependency?

level: principalimportance: should knowfreq 26%

basics

~20 s

Measure first: compile the whole rule set and count how many rules truly need the missing constructs. Rewriting a handful beats owning an engine with an unbounded worst case, a dependency to govern, and a rule dialect that is hard to walk back.

open as a page

What does regexp.QuoteMeta do to a string before you concatenate it into a Go pattern?

level: middleimportance: nice to knowfreq 32%

basics

~20 s

regexp.QuoteMeta returns a copy of the string with every regular-expression metacharacter backslash-escaped. Text such as v1.2(beta) then matches literally instead of being read as a pattern, and it can no longer break the surrounding pattern's syntax.

open as a page

In Go, why does regexp.MustCompile(`a|ab`).FindString("ab") return "a" rather than "ab"?

level: middleimportance: nice to knowfreq 30%

basics

~20 s

Go's regexp uses leftmost-first (Perl-style) semantics: among matches starting at the earliest position, it picks the one a backtracking search would find first, so the alternative a wins. Compile with regexp.MustCompilePOSIX, or call Longest, to get leftmost-longest instead.

open as a page

In Go, what is the difference between Regexp.ReplaceAllString and ReplaceAllLiteralString?

level: middleimportance: nice to knowfreq 38%

basics

~10 s

ReplaceAllString interprets dollar signs in the replacement text, expanding $1 and ${name} to captured groups. ReplaceAllLiteralString inserts the replacement byte for byte with no expansion, so a dollar sign stays a dollar sign.

open as a page