Regex to find repeated words ("the the")
\b(\w+)\s+\1\b with the gi flags finds a word followed by whitespace and the same word again. \1 is a backreference: it matches exactly the text that group 1 captured. Replace with $1 to remove the duplicate.
JavaScript ✓PCRE ✓
No catastrophic-backtracking patterns found. (A heuristic check: nested and adjacent repeats, overlapping alternatives.)
\bA word boundary: the edge between a word character and a non-word character (or the string's start or end)anchor(…)Capturing group #1: remembers what it matchedgroup\wA word character: a letter, digit or underscore (A-Z, a-z, 0-9, _)character type+Repeat it one or more times, as many as possible (greedy)quantifier\sA whitespace character (space, tab, line break and other Unicode spaces)character type+Repeat it one or more times, as many as possible (greedy)quantifier\1Backreference: the exact text that group #1 matchedbackreference\bA word boundary: the edge between a word character and a non-word character (or the string's start or end)anchor
1 capturing group.
Examples
Checked at build time in JavaScript and in Python's re.
| Input | Matches? |
|---|---|
the the cat | ✓ match |
The the cat | ✓ match |
this is is fine | ✓ match |
the cat | ✗ no match |
the theory | ✗ no match |
end
end | ✓ match |
that that's it | ✓ match |
bye-bye | ✗ no match |
Try your own text
One string per line: each line is tested on its own (the m flag is added here).
How it works
Group 1 captures a whole word, since \b forces the match to start at a word boundary. \s+ allows any whitespace between the two copies, including a line break, which is where doubled words usually hide in edited text. The final \b stops the theory from matching: after the comes o, a word character, so there is no boundary.
With the i flag, the backreference compares case-insensitively in both JavaScript and Python, so The the matches. s.replace(/\b(\w+)\s+\1\b/gi, "$1") keeps the first copy.
Variants
/(?<![\p{L}\p{N}_])([\p{L}\p{N}_]+)\s+\1(?![\p{L}\p{N}_])/giuUnicode-aware version for non-English text. Lookarounds replace\b, which is ASCII-only in JavaScript.
Gotchas
that that'smatches because the apostrophe is not a word character, so\bholds after the secondthat. Expect some false positives in English text.- Some doubled words are correct: "I knew that that was wrong" or "had had". Use this to flag for review, not to auto-fix.
\wand\bare ASCII-only in JavaScript, socafé caféis not found at all. Python 3 finds it, because its\wis Unicode-aware. Use the variant below for non-English text.- Repeated punctuation-separated words like
bye-byeare not caught, by design.
Related explainers
- Greedy vs lazy quantifiers: .* vs .*?How greedy .* and lazy .*? differ, shown on quoted strings, why a negated class like [^"]* is often better...
- Regex to trim leading and trailing whitespaceHow ^\s+|\s+$ with the g flag trims a string, why String.prototype.trim() is better, and the 2016 Stack...
- Regex to match or strip HTML tagsWhat <[^>]*> does to HTML, where it breaks (comments, > in attributes, scripts), and why stripping tags...