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\11is ambiguous — is it group 11, or group 1 followed by a literal1?\g{1}1says 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.
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.
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¶
- Make the doubled-word pattern case-insensitive, so it catches
The the. Does your engine's\1then matchTHEafterthe? (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.) - 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. - Now write (2) without a backreference. If your answer is
['"][^'"]*['"], check it against'it".
See also¶
- Forward references — the same
\1, written before the group exists, and the question of what an unset group means. - Backreferences in the replacement — the other
\1, which is not a backreference at all. - What a backreference costs — why Go and Rust's
regexrefuse the feature entirely. - Who supports what — the measured table across five engines.