Named Backreferences in Go: `\k<name>` Won't Compile — Here's the Fix

Go supports named capture groups but not \k<name> or (?P=name) references — the same RE2 limit, with a named-capture fix.

The errors that brought you here:

textOutput
error parsing regexp: invalid escape sequence: `\k`

or, if you came from Python syntax:

textOutput
error parsing regexp: invalid or unsupported Perl syntax: `(?P`

Here's the part that trips people up: Go happily supports named capture groups(?P<name>...) compiles fine and re.SubexpIndex("name") gives you clean access to what it captured. What Go doesn't support is referring back to a named group inside the same pattern — JS's \k<name>, Python's (?P=name). Same engine, same reason as numbered backreferences: RE2 guarantees linear-time matching by compiling to finite automata, and any backreference — named or numbered — requires backtracking. The name is cosmetic; the mechanism is the deal-breaker.

The workaround: named captures + verify in code

Identical shape to the numbered-backreference fix, but the named groups keep the Go side readable.

Matching a doubled word, JS/Python style:

textText
JS:      \b(?<word>\w+)\s+\k<word>\b
Python:  \b(?P<word>\w+)\s+(?P=word)\b

Go — capture two named groups, compare in code:

goGo
re := regexp.MustCompile(`\b(?P<first>\w+)\s+(?P<second>\w+)\b`)
first, second := re.SubexpIndex("first"), re.SubexpIndex("second")

for _, m := range re.FindAllStringSubmatch(text, -1) {
    if m[first] == m[second] {
        fmt.Println("duplicate:", m[first])
    }
}

SubexpIndex (Go 1.15+) resolves the group name to its index once, so the loop body reads almost like the JS original. This is the whole trick: the regex loosens ("any two words"), and Go enforces the equality the backreference used to.

A worked example: paired quotes with named groups

The delimiter-pairing idiom — match a quote, then require the same quote to close:

textText
JS: (?<q>['"]).*?\k<q>

Go, capture-and-verify:

goGo
re := regexp.MustCompile(`(?P<q>['"]).*?(?P<close>['"])`)
qi, ci := re.SubexpIndex("q"), re.SubexpIndex("close")

for _, m := range re.FindAllStringSubmatch(input, -1) {
    if m[qi] == m[ci] {
        // properly paired
    }
}

But note the better answer from the numbered-backreference guide applies here too: "[^"]*"|'[^']*' needs no backreference at all. Before porting a backreference, always ask whether alternation can replace it outright — it often can, and the result runs in every engine.

Syntax cheat sheet — named groups across engines

Define groupReference in patternReference in replacement
JavaScript(?<name>...)\k<name>$<name>
Python(?P<name>...)(?P=name)\g<name>
Go (RE2)(?P<name>...)❌ not supported${name}

Two details worth knowing:

  • Go accepts (?P<name>...) (Python style); Go 1.22+ also accepts (?<name>...) (JS style). Defining groups is portable — referencing them mid-pattern is not.
  • Go does support named groups in replacement strings: re.ReplaceAllString(s, "${name}") works fine. Only in-pattern backreferences are off the table. If your goal was rearranging text (swap (?P<last>\w+), (?P<first>\w+) into first last), you never needed an in-pattern backreference — plain ReplaceAllString does it:
goGo
re := regexp.MustCompile(`(?P<last>\w+), (?P<first>\w+)`)
out := re.ReplaceAllString("Ebert, Andrew", "${first} ${last}")
// "Andrew Ebert"

A surprising number of "I need named backreferences" cases are actually this — replacement, not in-pattern reference — and port to Go untouched.

Test it live

Try named capture groups and the duplicate word pattern side by side in JS, Python, and Go's actual RE2 engine in RegexPro's tester.


Related guides: Backreferences in Go · Lookarounds in Go · Python named groups in JavaScript

All guidesOpen the RegexPro tester →