This tutorial is for Go developers who want practical regular expressions in the standard library: matching text, compiling patterns safely, finding and extracting matches, replacing and splitting strings, and validating input with anchors. The API lives in the regexp package and uses RE2-style syntax, which differs from full PCRE. The page groups tasks (match, compile, find, extract, replace, split, validate) before syntax notes, unsupported features, performance, and pitfalls.
Go 1.24 on Linux.
Quick answer: what is regexp in Go?
Go exposes regular expressions through the regexp package. For a one-off check, use regexp.MatchString. For anything you run more than once, call regexp.Compile or regexp.MustCompile once and reuse the returned *regexp.Regexp methods such as MatchString, FindString, FindStringSubmatch, ReplaceAllString, and Split.
What is regexp in Go?
A regular expression is a pattern for matching, extracting, replacing, or splitting text. In Go you do that with the standard regexp package (people often say “regex”; the import name is still regexp).
regexp package vs regex
The package name is regexp; “regex” is just the generic term for the patterns you pass in.
import "regexp"When regex is useful
Reach for a regexp when the rule is pattern-shaped (digits anywhere, several separator styles, capture groups). Prefer strings for fixed literals—Contains, HasPrefix, HasSuffix, ReplaceAll, Split, TrimSpace—because the code stays obvious and fast to read.
| Task | Prefer |
|---|---|
| Fixed substring | strings.Contains |
| Prefix / suffix | strings.HasPrefix, strings.HasSuffix |
| Fixed replace / split | strings.ReplaceAll, strings.Split |
Example: strings.Contains(s, "error") beats a regexp for the same fixed word. When the rule depends on character classes, quantifiers, or grouping, use regexp instead of hand-rolled scans.
For simple membership checks, see strings.Contains in Go.
Match a string with regex
MatchString
MatchString returns whether the pattern matches anywhere in the string (a substring match), not the entire string unless you add anchors.
package main
import (
"fmt"
"regexp"
)
func main() {
ok, err := regexp.MatchString(`golinux`, `golinuxcloud`)
fmt.Println(ok, err)
}You should see true and a nil error.
Anchored vs substring match
Use ^ and $ when the whole string must match the pattern (common for validation).
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`^[a-z]+$`)
fmt.Println(re.MatchString(`abc`))
fmt.Println(re.MatchString(`Abc`))
}You should see true then false for the mixed-case value.
Case-sensitive matching
Matching is case-sensitive by default. For case-insensitive matching, prefix the pattern with (?i) (RE2 flag).
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`(?i)go`)
fmt.Println(re.MatchString("GO"))
}You should see true.
Compile regex patterns
Compile
Compile returns (*Regexp, error)—use it when the pattern string is not trusted (users, config files).
package main
import (
"fmt"
"regexp"
)
func main() {
_, err := regexp.Compile(`[`)
if err != nil {
fmt.Println("compile error:", err)
}
}You should see a line starting with compile error: (invalid pattern: unclosed character class).
MustCompile
MustCompile compiles the pattern and panics if the syntax is invalid. Use it for patterns you own in source code so mistakes fail at startup. Use Compile when the pattern comes from a user, config, or remote input so you can return the error.
package main
import (
"fmt"
"regexp"
)
var numberRE = regexp.MustCompile(`^[0-9]+$`)
func main() {
fmt.Println(numberRE.MatchString("42"), numberRE.MatchString("4a"))
}You should see true then false.
When to compile once and reuse
Compiling parses the pattern into a reusable *Regexp. Keep one compiled value for a fixed pattern and call methods on it in loops or hot paths.
Avoid compiling inside a loop:
for _, s := range values {
re := regexp.MustCompile(`^[a-z]+$`)
if re.MatchString(s) {
// matched
}
}Better:
re := regexp.MustCompile(`^[a-z]+$`)
for _, s := range values {
if re.MatchString(s) {
// matched
}
}Find matches
FindString
FindString returns the leftmost match, or an empty string if none—so it returns matched text, not a boolean. Use MatchString when you only need yes/no.
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`[0-9]+`)
fmt.Println(re.FindString("order 123 shipped"))
fmt.Println(re.FindString("no number here"))
}You should see 123 on the first line and an empty string on the second.
FindAllString
The second argument n caps how many matches are returned: n < 0 means all non-overlapping matches, n == 0 returns nil, n > 0 returns at most n matches.
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`[0-9]+`)
fmt.Println(re.FindString(`aaa123bbb`))
fmt.Println(re.FindAllString(`a1b22c`, -1))
fmt.Println(re.FindAllString(`a1b22c`, 1))
fmt.Println(re.FindAllString(`a1b22c`, 0))
}You should see four lines: 123, [1 22], [1], and [] (with n == 0, FindAllString returns nil, which prints as []).
FindStringIndex and FindAllStringIndex
FindStringIndex returns byte offsets [start, end) for the first match. FindAllStringIndex does the same for multiple matches, respecting the same n rules as FindAllString.
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`[0-9]+`)
fmt.Println(re.FindStringIndex(`aaa123bbb`))
}You should see [3 6] for the substring 123 in that input.
Extract groups and submatches
Capturing groups
Parentheses define numbered captures. The full match is index 0; later indexes are groups in left-to-right order.
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`user:([a-z]+),id:([0-9]+)`)
matches := re.FindStringSubmatch("user:deepak,id:42")
fmt.Println(matches)
}You should see [user:deepak,id:42 deepak 42] (full match, then each capture). Always check matches == nil before reading indexes when the input might not match.
FindStringSubmatch
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`(\d+)-(\d+)`)
fmt.Println(re.FindStringSubmatch(`id 10-20 end`))
}You should see a slice like [10-20 10 20] (full match, then each group).
FindAllStringSubmatch
Same layout as FindStringSubmatch, but for every match—useful when a line contains repeated key/value patterns.
Named groups
(?P<name>...) assigns a name for readability; the returned slice is still positional, in left-to-right order of opening parentheses.
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`(?P<major>\d+)\.(?P<minor>\d+)`)
fmt.Println(re.FindStringSubmatch(`v2.10`))
}You should see [2.10 2 10] for that version string.
Replace text with regex
ReplaceAllString
ReplaceAllString replaces every non-overlapping match. The replacement string can refer to captures with $1–$9 (see package docs for exact expansion rules).
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`[0-9]+`)
fmt.Println(re.ReplaceAllString(`a1b2`, `X`))
re2 := regexp.MustCompile(`(a)(b)`)
fmt.Println(re2.ReplaceAllString("xabx", "$2$1"))
}You should see aXbX and xbax.
Replace with captured groups
For logic that cannot be expressed as a fixed template, use ReplaceAllStringFunc and build the return value in Go.
Split string with regex
Split by one or more separators
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`[,;]+`)
fmt.Println(re.Split(`a,b;;c`, -1))
}You should see [a b c].
Limit number of results
A positive n limits how many substrings you get; the remainder of the input stays in the last element.
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`,`)
fmt.Println(re.Split(`a,b,c`, 2))
}You should see [a b,c].
Validate input with regex
Anchors for full-string validation
Use anchors when the whole input must match the pattern.
Without anchors, the regexp can match only part of the string.
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`\d{6}`)
fmt.Println(re.MatchString("123456"))
fmt.Println(re.MatchString("abc123456xyz"))
}You should see true twice: \d{6} can match six digits anywhere in the string.
For validation, use ^ and $ so the whole input must match:
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`^\d{6}$`)
fmt.Println(re.MatchString("123456"))
fmt.Println(re.MatchString("abc123456xyz"))
fmt.Println(re.MatchString("12345a"))
}You should see true, then false, then false—only an exact six-digit string passes.
Email-like or ID-style checks
Use regex for shape checks (length, allowed character set, required prefix). Email in production is policy-heavy—treat cookbook patterns as starting points, not a complete validator.
Go regex syntax notes
Go’s regexp package uses RE2-style syntax (predictable, linear-time matching—not full PCRE). For the full grammar, see RE2 syntax and regexp/syntax.
| Pattern | Meaning |
|---|---|
. |
any character except newline |
[0-9] / [^a-z] |
class / negated class |
+ * ? {3} {2,5} |
quantifiers |
^ $ |
start / end anchors |
( ) / (?: ) |
capturing / non-capturing group |
(?i) |
case-insensitive mode |
Alternation is written with | between subpatterns (often grouped with parentheses).
Word boundaries
\b is an ASCII word boundary. For example, \bgo\b matches go is simple but not golang is simple. With non-ASCII text, read the RE2 notes—boundary behavior is not “Unicode word” aware in the PCRE sense.
Unsupported or different regex features
Go’s engine does not support PCRE-only features such as lookbehind or backreferences inside the pattern (you can still use $1 style expansions in replacement strings where documented). Matching runs in linear time over the input size, which trades away some expressive power for predictable performance.
Performance tips
Compile the pattern once and reuse the Regexp for repeated scans. If you only need Contains, HasPrefix, or HasSuffix, prefer strings—see strings.Contains in Go for that workflow.
Common mistakes
- Treating
MatchStringas a full-string match without^and$. - Calling
Compileinside a tight loop instead of reusing oneRegexp. - Ignoring
Compileerrors for user-supplied patterns. - Mixing raw string literals
`...`(backticks) with escaped backslashes in double-quoted strings—raw strings are usually easier for regex. - Expecting Perl/PCRE lookaround or other unsupported constructs.
Go regexp cheat sheet
| Goal | API / pattern idea |
|---|---|
| One-off match | regexp.MatchString |
| Reuse pattern | regexp.Compile or MustCompile |
| First match text | FindString |
| All matches | FindAllString with n < 0 |
| Match positions | FindStringIndex, FindAllStringIndex |
| Capture groups | FindStringSubmatch, FindAllStringSubmatch |
| Named capture | (?P<name>...) |
| Replace | ReplaceAllString (with $1… where supported) |
| Split | Split with n for limit |
| Whole-string match | ^...$ anchors |
| Case-insensitive | (?i) prefix |
| Simple substring | prefer strings.Contains |
References
Summary
The regexp package is how you use regex in Go: MatchString answers whether a pattern matches a substring, while ^ and $ turn that into full-string validation. Compile versus MustCompile is the split between user-controlled patterns (handle errors) and fixed patterns (fail fast at init). FindString, FindAllString, and index variants return text or positions; FindStringSubmatch and named groups extract pieces. ReplaceAllString and Split finish most text pipelines. RE2 syntax omits some PCRE features, and compiling once keeps hot paths fast. For plain substring work, stay with strings before importing pattern complexity.

