Golang Regexp Tutorial: Match, Find, Replace, and Extract Regex Groups

Learn how to use regex in Go with the regexp package, including MatchString, Compile, MustCompile, FindString, submatches, ReplaceAllString, Split, and common mistakes.

Published

Updated

Read time 9 min read

Reviewed byDeepak Prasad

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.

go
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.

go
package main

import (
	"fmt"
	"regexp"
)

func main() {
	ok, err := regexp.MatchString(`golinux`, `golinuxcloud`)
	fmt.Println(ok, err)
}
Output

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).

go
package main

import (
	"fmt"
	"regexp"
)

func main() {
	re := regexp.MustCompile(`^[a-z]+$`)
	fmt.Println(re.MatchString(`abc`))
	fmt.Println(re.MatchString(`Abc`))
}
Output

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).

go
package main

import (
	"fmt"
	"regexp"
)

func main() {
	re := regexp.MustCompile(`(?i)go`)
	fmt.Println(re.MatchString("GO"))
}
Output

You should see true.


Compile regex patterns

Compile

Compile returns (*Regexp, error)—use it when the pattern string is not trusted (users, config files).

go
package main

import (
	"fmt"
	"regexp"
)

func main() {
	_, err := regexp.Compile(`[`)
	if err != nil {
		fmt.Println("compile error:", err)
	}
}
Output

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.

go
package main

import (
	"fmt"
	"regexp"
)

var numberRE = regexp.MustCompile(`^[0-9]+$`)

func main() {
	fmt.Println(numberRE.MatchString("42"), numberRE.MatchString("4a"))
}
Output

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:

go
for _, s := range values {
	re := regexp.MustCompile(`^[a-z]+$`)
	if re.MatchString(s) {
		// matched
	}
}

Better:

go
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.

go
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"))
}
Output

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.

go
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))
}
Output

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.

go
package main

import (
	"fmt"
	"regexp"
)

func main() {
	re := regexp.MustCompile(`[0-9]+`)
	fmt.Println(re.FindStringIndex(`aaa123bbb`))
}
Output

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.

go
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)
}
Output

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

go
package main

import (
	"fmt"
	"regexp"
)

func main() {
	re := regexp.MustCompile(`(\d+)-(\d+)`)
	fmt.Println(re.FindStringSubmatch(`id 10-20 end`))
}
Output

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.

go
package main

import (
	"fmt"
	"regexp"
)

func main() {
	re := regexp.MustCompile(`(?P<major>\d+)\.(?P<minor>\d+)`)
	fmt.Println(re.FindStringSubmatch(`v2.10`))
}
Output

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).

go
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"))
}
Output

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

go
package main

import (
	"fmt"
	"regexp"
)

func main() {
	re := regexp.MustCompile(`[,;]+`)
	fmt.Println(re.Split(`a,b;;c`, -1))
}
Output

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.

go
package main

import (
	"fmt"
	"regexp"
)

func main() {
	re := regexp.MustCompile(`,`)
	fmt.Println(re.Split(`a,b,c`, 2))
}
Output

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.

go
package main

import (
	"fmt"
	"regexp"
)

func main() {
	re := regexp.MustCompile(`\d{6}`)

	fmt.Println(re.MatchString("123456"))
	fmt.Println(re.MatchString("abc123456xyz"))
}
Output

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:

go
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"))
}
Output

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 MatchString as a full-string match without ^ and $.
  • Calling Compile inside a tight loop instead of reusing one Regexp.
  • Ignoring Compile errors 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.


Frequently Asked Questions

1. What is the difference between regexp.Match and regexp.MatchString?

Match tests a pattern against a byte slice. MatchString does the same for a string. For repeated checks, compile once with Compile or MustCompile and call methods on the returned Regexp.

2. Does MatchString require the whole string to match?

No. MatchString reports whether the pattern matches any substring unless you anchor the pattern with ^ and $ for a full-string check.

3. What does FindAllString return when n is 0 or negative?

With n less than zero, FindAllString returns all non-overlapping matches. With n equal to zero, it returns nil. With n greater than zero, at most n matches are returned.

4. How do I get capture groups in Go?

Use FindStringSubmatch or FindAllStringSubmatch. Index 0 is the full match; later indexes are captures in left-to-right order of opening parentheses. Named captures use (?P...) and appear in the same slice order.

5. Does Go use PCRE or Perl regex?

No. The regexp package uses RE2-style syntax and linear-time matching, which excludes some PCRE features such as lookbehind.

6. Should I use MustCompile?

Use MustCompile for patterns that are fixed at build time; it panics on invalid syntax. Use Compile when the pattern comes from a user or config so you can return the error.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive …