Skip to content

Limits and timeouts

Every bound regexutil applies, what it defaults to, and what happens when a caller pushes against it.

How long may a pattern be?

MaxPatternLength is 1024, and it is a byte count, not a character or rune count. A pattern is rejected when len(pattern) > MaxPatternLength, so 1024 bytes is accepted and 1025 is not — the cap is inclusive.

The check runs before anything else, including before the context is consulted, so an oversize pattern never reaches regexp.Compile and never allocates an automaton.

Because the cap counts bytes, a pattern written in a non-ASCII script hits it sooner than its length in characters suggests. 342 CJK characters are 1026 bytes and are rejected; the same 342 ASCII characters are nowhere near the cap.

Can I change the maximum pattern length?

No. MaxPatternLength is a Go const, not a variable, a field, or an option. There is no WithMaxLength, no package-level setter, and no environment variable. A consumer can read the value — and should, when composing an error message — but cannot raise or lower it.

If 1024 bytes is genuinely too small for your input, the pattern is probably not the right shape of configuration. A 1 KiB regex is already far larger than a filename glob or a search query; consider a list of shorter patterns compiled individually, which also gives the operator a better error when one of them is wrong.

How long may compilation take?

DefaultCompileTimeout is 100 ms, applied as a wall-clock bound around the regexp.Compile call itself. When it expires, the call returns ErrPatternCompileTimeout.

The bound covers compilation only. It does not limit how long you subsequently spend matching with the returned regex — see What regexutil does not do.

Can I raise the compile timeout above 100 ms?

No. CompileBoundedTimeout layers your timeout on top of the package's own, and context.WithTimeout on an already-bounded parent can only ever shorten the deadline. The effective bound is min(yours, 100ms).

Passing time.Hour does not buy a pathological pattern an hour: a pattern that takes longer than 100 ms to compile still fails at 100 ms with ErrPatternCompileTimeout.

What timeout actually applies?

Call Caller's deadline Effective bound
CompileBounded(ctx, p) none (context.Background()) 100 ms
CompileBounded(ctx, p) 25 ms 25 ms
CompileBounded(ctx, p) 10 min 100 ms
CompileBounded(ctx, p) already cancelled fails immediately
CompileBoundedTimeout(p, 25ms) 25 ms
CompileBoundedTimeout(p, 1h) 100 ms
CompileBoundedTimeout(p, 0) fails immediately

The rule behind every row: the shortest deadline in play wins, and 100 ms is always in play.

What happens if I pass a zero or negative timeout?

Every call fails with ErrPatternCompileTimeout, including on a trivially valid pattern. CompileBoundedTimeout(pattern, 0) builds a context that is already expired, so the compile has no budget at all. CompileBoundedTimeout("hello", 0) and CompileBoundedTimeout("hello", -1) both return the timeout error.

There is no validation and no special case for zero: if you are deriving the timeout from configuration, clamp it to a positive value yourself before calling.

What happens if the context is already cancelled?

The call returns ErrPatternCompileTimeout, promptly, whatever the pattern.

This is the same error a genuine compile timeout produces, so a caller cannot tell the two apart from the error alone, and errors.Is(err, context.Canceled) is false — the context's own error is not part of the chain. If you need to distinguish "we are shutting down" from "this pattern is pathological", check ctx.Err() yourself:

re, err := regexutil.CompileBounded(ctx, pattern)
if err != nil && ctx.Err() != nil {
    // The caller's context ended it — not a verdict on the pattern.
    return ctx.Err()
}

What happens if I pass a nil context?

The call panics with cannot create context from nil parent, from context.WithTimeout. regexutil does not nil-check the context. Pass context.Background() when you have nothing better, or use CompileBoundedTimeout, which builds the context for you.

What happens to an empty pattern?

An empty pattern compiles successfully and produces a regex that matches every input, including the empty string. This is regexp.Compile("") behaviour and regexutil does not change it.

It matters because an unset config key usually arrives as "". If an empty pattern would mean "match nothing" or "feature disabled" in your tool, check for it before calling — regexutil will not report it as an error.

How long does compiling actually take?

Ordinary patterns are far below the bound; repetition-heavy patterns at the cap are not. Measured with Go 1.26.5 on a Linux developer machine, filling the full 1 KiB cap by repeating the unit shown, nine runs each:

Pattern shape (repeated to ~1020 bytes) Compile time (avg) Timed out
hello\|world\| — plain alternation 0.13 ms 0 / 9
^\w+@[a-z]+\.[a-z]{2,}$ — anchored classes 0.18 ms 0 / 9
[a-z]{0,1000} — bounded repeat of a class 29 ms 0 / 9
a{0,1000} — bounded repeat of a literal 35 ms 0 / 9
(a\|b){0,900} — bounded repeat of a group 45 ms 0 / 9
(a\|bb\|ccc){0,999} 78 ms 3 / 9
(abc\|def\|ghi){0,999} 105 ms 4 / 9

Two things follow, and both are worth knowing before you set an operator's expectations:

  • Normal patterns have enormous headroom. A realistic 1 KiB pattern compiles in a fraction of a millisecond — three orders of magnitude under the bound.
  • The timeout is reachable by valid input, not only by attacks. A syntactically fine pattern that stacks many large bounded repetitions can take longer than 100 ms at the cap, and will be rejected as though it were pathological. On a slower or heavily loaded machine the threshold arrives sooner. Treat ErrPatternCompileTimeout as "too expensive here, right now", not as proof of malice — and let the operator see which pattern was rejected so they can simplify it.

Reproduce them by timing regexp.Compile on the same inputs on your own hardware — the repo ships no benchmark for this, and the numbers above are illustrative of shape, not a guarantee.

Which patterns does Go itself reject?

regexutil adds bounds; it does not change the syntax regexp.Compile accepts. Go's own limits still apply and surface as ErrPatternInvalid, not as a regexutil bound. The one people meet most often is the repeat count:

  • a{1000} and a{0,1000} compile.
  • a{1001} fails with invalid repeat count: `{1001}`.
  • Nested repeats whose product exceeds 1000 fail the same way — ((a{100}){100}){100} is rejected before any bound of ours is consulted.

That limit is Go's, documented in regexp/syntax, and it is a large part of why compile-time blowups in Go are bounded at all.