Skip to content

What a backreference is

One line: \1 matches the text group 1 captured — not the pattern again.

Level: 101 · anyone who has written a pattern with ( ) in it

Keywords on this page: \1 · (…) · (?<name>…) · $1 in the replacement — Topics: Extracting fields · Command-line tools

That one sentence is the whole idea, and almost every surprise later in this chapter comes from forgetting the word text. (\w+) \1 does not mean "a word, then another word". It means "a word, then that same word, character for character".

The smallest useful example

Finding a doubled word — the the the that survives every proofread — is what a backreference is for. Nothing else in the pattern language can say it, because a plain pattern has no memory of what it just matched.

Verified output of doubled_word_py.py — regenerated by tools/run_examples.py, never hand-typed.

numbered   offset  0  'the the'  group 1 = 'the'
numbered   offset 20  'fox fox'  group 1 = 'fox'
numbered   offset 49  'dog dog'  group 1 = 'dog'
named      offset  0  'the the'
named      offset 20  'fox fox'
named      offset 49  'dog dog'
two different words: False
unset group:         False

Two things in that output are worth naming now.

two different words: False is the point of the lesson: the quick is two words with a space between them, and the pattern rejects it. The engine captured the, and then demanded the again.

unset group: False is the trap that the rest of the chapter is built on. The pattern (z)?\1 makes group 1 optional, so against an empty string the group never takes part. What should \1 do then? Python fails the match. JavaScript, alone among the engines here, matches the empty string — see forward references.

The same idea, spelled six ways

Python

Numbered \1, or named with Python's own spelling, (?P<word>…) and (?P=word). Naming is worth the keystrokes: a group's number changes the moment somebody adds a ( above it, and a pattern that silently starts matching the wrong thing is worse than one that breaks.

Perl — where the syntax comes from

Verified output of doubled_word_pl.pl — regenerated by tools/run_examples.py, never hand-typed.

numbered   offset  0  'the the' group 1 = 'the'
numbered   offset 20  'fox fox' group 1 = 'fox'
numbered   offset 49  'dog dog' group 1 = 'dog'
g{1}      matched
relative   matched
named      matched
unset group: failed

Perl has the widest vocabulary here, and two of its forms solve real problems:

  • \g{1} exists because \11 is ambiguous — is it group 11, or group 1 followed by a literal 1? \g{1}1 says the second one without doubt. Python spells this \g<1> in a replacement (see backreferences in the replacement), but has no equivalent inside a pattern.
  • \g{-1} counts backwards from where it is written, so a fragment stays correct when it is pasted into a bigger pattern with other groups above it.

JavaScript

Verified output of doubled_word_js.js — regenerated by tools/run_examples.py, never hand-typed.

numbered   offset  0  'the the'  group 1 = 'the'
numbered   offset 20  'fox fox'  group 1 = 'fox'
numbered   offset 49  'dog dog'  group 1 = 'dog'
named      true
unset group: empty match

Same \1, same \k<word> as Perl — and one genuine difference on the last line. A backreference to a group that never participated matches the empty string in JavaScript, where Python, Perl, Ruby and Java all fail the match. It is a one-line difference that changes what whole patterns mean, which is why it gets its own treatment in the next lesson.

Go — and the first engine that says no

Verified output of doubled_word_go.go — regenerated by tools/run_examples.py, never hand-typed.

compile: error parsing regexp: invalid escape sequence: `\1`
doubled by hand: "the the"
doubled by hand: "fox fox"

Go's regexp is RE2. It refuses \1 at compile time, and not as an oversight: RE2 promises to answer in time linear in the subject, and backreferences are precisely the feature that promise cannot survive. What a backreference costs measures why.

The second half of that output is the idiom you actually write in Go: capture two groups, then compare them in the host language. It is more code and it is never exponential.

Rust

Rust has no regular expressions in its standard library, so the question is really about crates — and the two common ones disagree.

Verified output of doubled_word_rs_sh.sh — regenerated by tools/run_examples.py, never hand-typed.

regex:       refused
regex parse error:
    \b(\w+) \1\b
            ^^
error: backreferences are not supported
fancy-regex: found "the the" at byte 0
fancy-regex: found "is is" at byte 21

regex ↗ is the default choice and takes RE2's side: no backreferences, linear time guaranteed. fancy-regex ↗ wraps it with a backtracking layer, gains backreferences and lookaround, and gives up the guarantee. If you have been told "Rust's regex doesn't support backreferences", this is the whole of what that means: swap the crate and you have them.

grep and sed — the oldest spelling

Verified output of doubled_word_grep_sh.sh — regenerated by tools/run_examples.py, never hand-typed.

BRE  (grep):
the the cat
fox fox
ERE  (grep -E):
the the cat
fox fox

In a POSIX basic regular expression the parentheses are backslashed — \(…\) — and \1 is required by the standard. In an extended one (grep -E), POSIX does not require backreferences at all; GNU and BSD both provide them anyway, which is why the shell one-liner you copied works everywhere you have tried it and is still, on paper, unportable.

The grep on your PATH may not be /usr/bin/grep

This example calls /usr/bin/grep by its full path on purpose. On a machine where something else — ugrep, say — has been installed as grep, the same BRE can be rejected outright: \(a\)\1 came back as invalid escape while this lesson was being written. When a pattern that "definitely works" doesn't, check which binary answered.

ABAP

Since release 7.55, ABAP's FIND and REPLACE take a PCRE addition, and the pattern is handed to the PCRE2 library built into the kernel. That means ABAP's answer to this lesson is Perl's answer: \1 in the pattern, $1 in the replacement.

z_doubled_word.prog.abap in full — pasted here by tools/run_examples.py from the file CI runs.

REPORT z_doubled_word.

" Backreferences in ABAP, in the PCRE syntax available as of release 7.55.
" Nothing in this repository can run ABAP, so this file is checked by abaplint
" and printed here as source only -- there is no recorded output beside it.

DATA(text) = `the the quick brown fox fox jumped`.

" \1 is the text group 1 captured, exactly as in Perl: the PCRE addition hands
" the pattern to the PCRE2 library built into the ABAP kernel.
FIND ALL OCCURRENCES OF PCRE `\b(\w+) \1\b`
     IN text RESULTS DATA(matches).

LOOP AT matches INTO DATA(match).
  WRITE / |{ match-offset } { substring( val = text
                                         off = match-offset
                                         len = match-length ) }|.
ENDLOOP.

" The older REGEX addition understands \1 too, but it is obsolete as of 7.55 --
" and abaplint enforces that, which is why there is no REGEX statement here.

Nothing in this repository can run ABAP, so this file has no recorded output — it is checked by abaplint and pasted here as source. What the engine underneath it does can be shown, because PCRE2 is on this machine too:

Real run — PCRE2 10.48 (pcre2grep), macOS 26.6.2 x86-64, 2026-09-21. The library ABAP's PCRE addition calls, driven from the shell.

$ pcre2grep -on "\b(\w+) \1\b" subject.txt
3:the the

The older REGEX addition is a different engine (POSIX-style, Boost-based) and has been obsolete since 7.55. New ABAP uses PCRE; the two do not always agree on the edges, so a pattern moved from one to the other deserves a test rather than a guess.

The digit trap, in every language at once

\1 is fine. \10 is a question: group 10, or group 1 then a literal zero? Every engine answers it by its own rule, and none of them will warn you.

You want Perl Python (pattern) JavaScript PCRE2 / ABAP
group 1, then 0 \g{1}0 (no form — use a named group) (no form — use a named group) \g{1}0
group 10 \10 \10 \10 \10
by name \k<w>, \g{w} (?P=w) \k<w> \k<w>, (?P=w)

The practical rule: past nine groups, use names. It is the only spelling that is unambiguous in every engine on this page.

Try it

  1. Make the doubled-word pattern case-insensitive, so it catches The the. Does your engine's \1 then match THE after the? (In Python, Perl, JavaScript and PCRE2 it does — the flag applies to the comparison, not just the literal text. That is a design decision, not an obvious consequence.)
  2. Write a pattern that matches a quoted string where the opening and closing quote are the same character, '…' or "…", but not '…". That is one backreference and is the second-most-common real use.
  3. Now write (2) without a backreference. If your answer is ['"][^'"]*['"], check it against 'it".

See also