Groups & backreferences
Capture regex groups, name them, reuse them in replacements, trace backreferences, handle alternatives, and highlight match ranges with indices.
- 01Read every slot in a matchFind
match[0], each numbered group, each named group, and theundefinedof a skipped group. - 02Use saved text againUse
$1,$<name>, and replacer functions inreplace(), and\1and\k<name>inside a regex. - 03Pick the right kind of groupGroup choices with
|, skip saving when you don't need it, check browser support, and find where each group is with thedflag.
Save pieces of a match and use them again
A regular expression, or regex, is a pattern for finding text. So far, a regex told you two things: did it match, and what text did it match. This lesson shows how a regex can also save smaller pieces of that text. Then you can use those pieces again.
A capturing group is a part of a regex inside round brackets, like (\d{4}). When the regex matches, the group saves the text that its part matched. This saved text is called a capture.
You can use a capture again in three ways:
- Read it from the match result, like
match[1]. - Put it into new text with
replace(), using a token like$1or$<name>. A token is a short code that gets swapped for the saved text. - Ask for the same text again, later in the same regex. This is a backreference, like
\1or\k<name>.
This table lists the four kinds of round-bracket groups in this lesson. Read each row from left to right: the name, how you write it, what it does, and when to use it.
| Name | How you write it | What it does | When to use it |
|---|---|---|---|
| Capturing group | (...) | Saves the text it matched in match[1], match[2], and so on. | Split a date into year, month, and day. |
| Non-capturing group | (?:...) | Groups pieces together, but saves nothing and takes no number. | Use ?, +, or | on several pieces at once. |
| Named group | (?<year>...) | Saves the text by number and by name, in match.groups.year. | Read parts by clear names, and use $<year> in replacements. |
| Lookaround | (?=...), (?<=...) | Checks the text next to a spot, without making it part of the match. | You will learn it in the next lesson. |
This lesson is only about groups. Other lessons cover the rest:
- How to make a regex, flags, and the methods
test,exec,match,matchAll, andreplace: Patterns & flags. - Repeat marks like
+and*: Quantifiers. - Copying the properties of an object into variables, which helps with named groups: Destructuring.
Picture a paper form for a shipping date. It has three boxes: year, month, and day. Someone reads a date and writes each part in its own box. Later you can read any box on its own. You can copy a box onto another form. You can also check that the order number is written the same way in two places.
- In real life: The form has a box for year, month, and day
- In JavaScript: The regex has a group for year, month, and day
- In real life: The boxes are numbered 1, 2, 3 from left to right
- In JavaScript: The groups fill
match[1],match[2], andmatch[3]in order - In real life: A box with a printed label, like Year, is easier to find than Box 1
- In JavaScript: A named group like
(?<year>...)is easier to read than$1 - In real life: Check that the order number is written the same way twice
- In JavaScript: A backreference asks for the same text again
Numbered groups: how exec() fills the result
STEP THROUGHGroups get numbers in the order of their opening round bracket, (, counted from left to right. In /(\d{4})-(\d{2})-(\d{2})/:
- the year,
(\d{4}), is group 1; - the month is group 2;
- the day is group 3.
exec() runs the regex on some text and gives back a match result. The match result is a list. match[0] holds the whole match. match[1], match[2], and so on hold the groups.
2026-09-26 in a numbered slot. The hyphens have no brackets, so they get no slot.const match = /(tea) (milk)/.exec("buy tea milk");console.log(match[0], match[1], match[2]);console.log(match.index);console.log(match.groups);Line 1 runs exec() on "buy tea milk". The regex has two groups, (tea) and (milk). Line 2 prints tea milk tea milk: first the whole match, then group 1, then group 2.
Line 3 prints 4. match.index is the position where the match starts. Positions count from 0, so the t of tea is at 4. Line 4 prints undefined. match.groups holds named groups, and this regex has none.
Step through exec() and watch the groups fill the match result.
script
const text = "date: 2026-09-26";const match = pattern.exec(text);console.log(match[0]);console.log(match[1]);console.log(match[2]);console.log(match[3]);Step through the code above. The first ( on line 1 starts the year group. So after exec() finds a match, match[1] is "2026". The second ( starts match[2], and the third starts match[3]. The hyphens and the digit codes like \d{2} get no slots. Only round brackets make groups.
const pattern = /file-(\d+)(?:-(draft))?/;console.log(pattern.exec("file-42")[1]);console.log(pattern.exec("file-42")[2]);console.log(pattern.exec("file-42-draft")[2]);The ? at the end of line 1 makes the -(draft) part optional. (The ?: makes a group that saves nothing. You will meet it later in this lesson.) So the (draft) group can be used or skipped.
- Line 2 prints
42, the number that group 1 saved. - Line 3 prints
undefined. The textfile-42has no-draft, so the(draft)group was skipped. - Line 4 prints
draft, becausefile-42-drafthas it.
A skipped group is not the same as a failed match. If the whole regex fails, exec() returns null, and there are no slots at all.
const skipped = /(tea)?milk/.exec("milk");const empty = /(te*)/.exec("te");console.log(skipped[1]);console.log(empty[1]);console.log(/((?:tea)*)/.exec("")[1]);Line 3 prints undefined. The text is only milk, so the optional (tea) group was skipped. Line 4 prints te, the two letters the group saved. Line 5 prints an empty string, "". The outer group was used, but the * inside it repeated zero times, so it saved no letters.
So undefined means the group was skipped. An empty string means the group was used but saved nothing.
Named groups and replacements
REAL OUTPUTA named group has a name as well as a number. You write it as (?<name>...). The match result then gets a groups object, and each name is a property on it, like match.groups.year.
Names make long regexes easier to read. You can also use destructuring to copy the names you need into variables: const { year } = match.groups.
const match = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/.exec("2026-09-26");const { year, month, day } = match.groups;console.log(year);console.log(day + "/" + month + "/" + year);console.log(match[1], match.groups.year);Line 1 names three groups: year, month, and day. Line 2 copies them out of match.groups into three variables. Line 3 prints 2026. Line 4 puts the parts in a new order and prints 26/09/2026. You don't need to remember that the day was group 3.
Line 5 prints 2026 2026. A named group still has a number too, so match[1] and match.groups.year hold the same text.
matchAll()const text = "Ship 2026-09-26, return 2027-01-05";const pattern = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/g;const dates = [...text.matchAll(pattern)].map(({ groups }) => groups.month + "/" + groups.day + "/" + groups.year,);console.log(dates.join(", "));exec() gives you one match. matchAll() gives you every match, each with its own groups. It needs the g flag, which means "find all matches".
- Line 2 adds
g, so both dates are found. - On line 3,
({ groups })takes thegroupsobject out of each match. - Lines 3 to 5 turn each
groupsobject into a date like09/26/2026. - Line 6 prints
09/26/2026, 01/05/2027.
Plain match() with g is different. It gives only the matched text of each match, without the groups.
matchAll() gives back an iterator: a list you can read only once, from start to end. Spread it into an array with [...], or loop over it once. If the regex has no g flag, matchAll() throws a TypeError.
Groups also work in replace(). In a replacement string, $1 means "the text group 1 saved". $2 means group 2, and so on. So "$2/$3/$1" writes the month, then the day, then the year.
$ number is swapped for the text that group saved. The slashes are plain text, so they stay.Step through three ways to use groups in replace(): $1, $<name>, and a replacer function.
script
console.log(iso.replace(/(\d{4})-(\d{2})-(\d{2})/, "$2/$3/$1")); const person = "Lovelace, Ada";const namePattern = /(?<last>\w+), (?<first>\w+)/;console.log(person.replace(namePattern, "$<first> $<last>"));console.log(person.replace(namePattern, (full, last, first, offset, input, groups) => { return groups.first.toUpperCase() + " " + groups.last.toUpperCase() + " at " + offset;}));const text = "tea milk";console.log(text.replace(/(tea) (milk)/, "$2 $1"));console.log(text.replace(/(tea) (milk)/, "$$1 $2"));console.log(text.replace(/(tea) (milk)/, "$&"));- Line 2 prints
milk tea.$2puts in group 2, then$1puts in group 1. - Line 3 prints
$1 milk.$$puts in one plain dollar sign, so the1after it is just text. - Line 4 prints
tea milk, because$&puts in the whole match.
These $ codes only work in a replacement string. Inside the regex itself, you write \1 instead. You will see it in the backreferences section.
You can also pass a function to replace() instead of a string. This is called a replacer function. JavaScript calls it once for each match and uses what it returns. It gets these arguments, in this order:
- the whole match;
- the text of each group, one argument per group;
- the position where the match starts;
- the whole input text;
- the
groupsobject, if the regex has named groups.
Use a function when the new text needs some work, such as a check or a change to capital letters.
exec() returns null when there is no match. Reading .groups from null throws an error. In real code, check first: if (match?.groups) { const { year } = match.groups; }. The examples here skip the check, because their text always matches.
Groups that save nothing, and the | choice
SORT ITRound brackets do two jobs at once:
- They group pieces, so a mark after them works on the whole group. For example,
|means "or", and+means "one or more". - They save the text, which uses up a slot number.
Sometimes you only need the first job. Then write (?:...). This is a non-capturing group: it groups, but it saves nothing and takes no number. Your other groups keep their numbers.
const capturing = /^(cat|dog)-(food|toy)$/;const nonCapturing = /^(?:cat|dog)-(food|toy)$/;console.log(capturing.exec("cat-food")[1]);console.log(capturing.exec("cat-food")[2]);console.log(nonCapturing.exec("cat-food")[1]);console.log(/(\d)+/.exec("123")[1]);- Line 1 has two capturing groups.
(cat|dog)is group 1 and(food|toy)is group 2. Lines 3 and 4 printcatandfood. - Line 2 uses
(?:cat|dog). It groups the two animals but saves nothing. So(food|toy)becomes group 1, and line 5 printsfood. - Line 6 shows one more rule. When a group repeats, it keeps only its last piece.
/(\d)+/matches123, but group 1 holds only"3".
The next example uses two more marks. ^ means "the start of the text", and $ means "the end of the text".
const loose = /cat|dog/;const anchoredWrong = /^cat|dog$/;const anchoredRight = /^(?:cat|dog)$/;for (const word of ["cat", "dog", "catfish", "hotdog", "bird"]) { console.log(word + ": " + loose.test(word) + " " + anchoredWrong.test(word) + " " + anchoredRight.test(word));}The loop on line 4 tests five words. Each printed line shows three results, in this order: loose, anchoredWrong, and anchoredRight.
The | splits the whole pattern into two halves. So ^cat|dog$ means "cat at the start" or "dog at the end". That is why catfish and hotdog give true in the middle result.
To mean "the whole text is exactly cat or dog", group the two words first: /^(?:cat|dog)$/. The next lesson covers ^ and $ in more depth.
| splits the whole pattern in two, so hotdog passes. The group (?:cat|dog) puts both words between ^ and $.`/(\d{4})-(\d{2})-(\d{2})/``/^(?:cat|dog)$/``/\b(?<word>\w+)\s+\k<word>\b/i``/cat|dog/``text.replace(pattern, "$<first> $<last>")``/(?<quote>['"]).*?\k<quote>/`
Put each pattern under the idea that does the main work. Read the explanation after each one, even when you are right.
Backreferences: match the same text again
A backreference asks for the text a group saved, again, later in the same regex. It does not repeat the pattern. It repeats the text. If group 1 saved cat, the backreference must find cat. There are two ways to write one:
\1points to group 1. In the same way,\2points to group 2.\k<name>points to a named group.
Backreferences can find a word typed twice. They can also check that a quote ends with the same quote mark it started with.
const repeated = /(\w+)\s+\1/i;console.log(repeated.test("Tea tea"));console.log(repeated.test("Tea coffee"));const strict = /(\w+)\s+\1/;console.log(strict.test("Tea tea"));console.log(repeated.test("Tea teatime"));console.log(/\b(\w+)\s+\1\b/i.test("Tea teatime"));In line 1, (\w+) saves one word. (\w is a letter, a digit, or _.) Next, \s+ means one or more spaces. Then \1 asks for the saved word again. The i flag at the end means "ignore case", so Tea and tea count as the same.
- Line 2 prints
true. - Line 3 prints
false, becausecoffeeis a different word. - Line 5 prints
false. Thestrictregex on line 4 has noiflag, soTeaandteaare different. - Line 6 prints
trueforTea teatime. The regex only needsteaat the start of the second word. - Line 7 adds
\b, which means "the edge of a word". Now the second word must end right aftertea, so line 7 printsfalse.
\1 asks for that same word again. The i flag ignores case, so Tea and tea count as the same.const repeated = /\b(?<word>[A-Za-z]+)\s+\k<word>\b/i;console.log(repeated.test("Please please stop"));console.log("Please please stop".replace(repeated, "$<word>")); const quoted = /(?<quote>['"])(?<text>.*?)\k<quote>/;console.log(quoted.exec("say \"hi\" and 'bye'").groups.text);console.log(quoted.test("\"mismatch'"));- Line 1 saves a word in a group named
word. Then\k<word>asks for the same word after the spaces. Line 2 printstrue. - Line 3 swaps the two words for one copy,
$<word>, and printsPlease stop. - Line 5 saves the opening quote mark,
'or", in a group namedquote. The\k<quote>at the end asks for that same mark to close it. Line 6 printshi. - Line 7 prints
false. The text"mismatch'starts with"but ends with'.
With a backreference, the regex must remember saved text while it searches. That is useful, but it costs time. Don't try to read big nested formats, like HTML, with one clever regex. A later lesson on regex speed shows when a pattern gets too slow.
The same group name in two choices
FEATURE DETECTFor a long time, JavaScript did not allow two groups with the same name. The ES2025 version of JavaScript allows it in one case: when the two groups are on different sides of a |. Only one side can match, so only one group with that name is ever used.
This helps when one date format puts year first and another puts year last.
This table shows where this works today. Read each row from left to right: what you try, whether it works, and what to do.
| What you try | Does it work? | What to do |
|---|---|---|
The same group name on both sides of | | Yes, in modern browsers (ES2025). | MDN lists Chrome 125+, Firefox 129+, and Safari 17+. The Chrome 154 used by this site supports it. |
| The same pattern in Node.js 22.23.1, which this project uses | No, not here. | Building the regex throws SyntaxError: Duplicate capture group name. So the lesson keeps the pattern in a string and checks support first. |
| The same pattern in your own code | Only where it is supported. | Wrap new RegExp(source) in try/catch when not every browser or runtime supports a feature. |
const source = "(?<year>\\d{4})-\\d{2}|\\d{2}/(?<year>\\d{4})";try { const pattern = new RegExp(source); console.log(pattern.exec("2026-09").groups.year); console.log(pattern.exec("09/2026").groups.year);} catch (error) { console.log(error.name + ": " + error.message);}checkingwaiting for browserThe check runs in your browser after the page loads.
new RegExp() runs, and catch handles that.MDN's browser data says current Chrome, Firefox, and Safari support it. But this site's tests run on Node 22.23.1, and Node 22 still throws Duplicate capture group name. So this lesson does three things:
- It keeps the pattern in a string, not in a regex written between slashes.
- It builds the regex with
new RegExp()insidetry/catch, so an error can be caught. - It checks the browser result in a separate test.
Trying a feature and catching the error is called feature detection.
Match indices: where each group starts and ends
PLAYGROUNDThe d flag adds an indices list to the match result. It tells you where each piece is in the text:
match.indices[0]is where the whole match starts and ends.match.indices[1]is where group 1 starts and ends.match.indices.groups.yearis where theyeargroup starts and ends.
Each one is a pair, [start, end]. The end is the position just after the last character. This is the same rule as slice(start, end).
const pattern = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/d;const match = pattern.exec("Created 2026-09-26");console.log(match.indices.groups.year);[8, 12]
The d flag adds indices. Each pair is [start, end]. The end is the position just after the last character, the same as in slice(start, end).
const pattern = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/d;const match = pattern.exec("Ship on 2026-09-26.");console.log(match.indices[0].join(".."));console.log(match.indices.groups.year.join(".."));console.log(match.indices.groups.month.join(".."));console.log(match.indices.groups.day.join(".."));- Line 3 prints
8..18. The date starts at position 8 and ends just before 18. - Line 4 prints
8..12for the year. - Lines 5 and 6 print
13..15and16..18for the month and the day.
These numbers are positions in the original text. But a position is not always the number of characters you see on screen. The next example shows why.
const text = "\u{1D11E} tea";const match = /(?<item>tea)/d.exec(text);console.log(match.indices.groups.item.join(".."));console.log(text.slice(...match.indices.groups.item));Line 1 starts the text with a music symbol, written as \u{1D11E}. You see one symbol. But JavaScript stores text in small pieces called UTF-16 code units, and this symbol takes two of them. Then comes a space. So tea starts at position 3, not 2.
Line 3 prints 3..6. Line 4 passes the same pair to slice() and prints tea. So pass the positions straight to slice(). Don't count the characters you see.
try { const pattern = new RegExp("(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})", "d"); const match = pattern.exec("Created 2026-09-26 by Ada"); console.log(match?.indices?.groups ?? match?.indices ?? "no match");} catch (error) { console.log(error.name + ": " + error.message);}Created 20261: year-092: month-263: day by Ada
- Group 1 · year
"2026"8..12 - Group 2 · month
"09"13..15 - Group 3 · day
"26"16..18
The whole match is 2026-09-26. Each highlight uses a start and end from match.indices, so it covers exactly the right characters.
try/catch and runs with the d flag, so each group gives its start and end.This is how a regex can help a web page. A log viewer can save the time, level, and message of each line in groups. Then it can highlight exactly those positions, without guessing lengths. The playground above limits how long the text and the pattern can be. It also catches a broken pattern, so your edits can't crash the page.
Where groups and backreferences show up
Groups help most when you do something with the pieces after the match. If you only need a yes or no, a regex without groups is often enough. Groups are worth it when you want to read, replace, highlight, or compare the pieces.
Each row of this table is a real task. The second column says how groups help with it.
| Task | How groups help |
|---|---|
| Reading dates | Save the year, month, and day. Then check them or write them in a new order. |
| Log lines | Save the time, level, and message. Leave out the marks between them. |
| Formatting names | Turn Last, First into First Last with named tokens or a replacer function. |
| Cleaning up text in an editor | Find a word typed twice with a backreference, and remove the extra copy. |
| Highlighting on a page | Use match.indices.groups to mark exactly where each group is in the text. |
A log line is one line of notes that a program writes about what it did. The next example reads two parts of one.
const line = "INFO: user=Asha";const pattern = /(?<level>[A-Z]+): user=(?<user>\w+)/;const match = pattern.exec(line);console.log(match.groups.level);console.log(match.groups.user);Line 2 names two groups: level and user. Line 3 runs the regex. Lines 4 and 5 read the two groups by name and print INFO and Asha.
For a full log format with many rules, use a parser instead of one big regex. A parser is a program that reads a format step by step.
const cart = "tea: 3, milk: 5";const item = /(?<name>\w+): (?<price>\d+)/g;const labels = cart.replace(item, (full, name, price) => name + " costs $" + price);console.log(labels);Line 2 uses the g flag, so replace() changes both items. Line 3 passes a replacer function. For each match, it gets the whole match, then the name group, then the price group. It returns a new label. Line 4 prints tea costs $3, milk costs $5. Use a function like this when the new text needs to be built, not just copied.
Three more places where groups help:
- Checking a whole form field:
exec()also finds matches in the middle of longer text. Put^and$around the whole pattern when extra characters are not allowed. - Cleaning up text: a backreference can find a word typed twice. Then an editor can turn
the theintothe. - Highlighting:
indices.groupstells a web page exactly where the year, month, day, or quoted text is.
Common misconceptions
- "Round brackets always mean I want the text later." Not always. Use
(?:...)when you only need to group, for|or a repeat mark like+. - "Group numbers follow how close the groups look." Numbers follow the order of the opening
(, from left to right. This is true even for a group inside another group, and for a group that gets skipped. - "A skipped optional group gives an empty string." It gives
undefined. An empty string means the group was used but saved no characters. - "
^cat|dog$means exactly cat or dog." The|splits the whole pattern into^catordog$. Write^(?:cat|dog)$for exact words. - "A backreference repeats the pattern." It repeats the saved text. If group 1 saved
"cat",\1must find"cat". - "If browsers support a regex feature, Node does too." The same group name in two choices works in modern browsers. This project's Node 22 still rejects it.
This table compares four kinds of groups. Read down a column to see how one kind behaves.
| Question | Capturing (...) | Non-capturing (?:...) | Named (?<name>...) | Lookaround (next lesson) |
|---|---|---|---|---|
| Is its text part of the match? | Yes. | Yes. | Yes. | No. It only checks the text next to a spot. |
| Does it save text in a slot? | Yes, in a numbered slot. | No. It saves nothing. | Yes, in a numbered slot and under its name. | No. It saves nothing. |
| When should you use it? | When you need the text later. | When you only need to group. | When you want parts with clear names. | When you need to check nearby text without taking it. |
| Where is it taught? | In this lesson. | In this lesson. | In this lesson. | In the next lesson. |
Practice exercises
5 EXERCISESWhat does this code print? Type the exact value.
const match = /(\d{4})-(\d{2})-(\d{2})/.exec("2026-09-26");
console.log(match[2]);The second opening ( starts the month group. So match[2] is 09.
Type the value that the code prints for the optional group.
const match = /file-(\d+)(?:-(draft))?/.exec("file-42");
console.log(match[2]);The (draft) group is in the regex, but file-42 has no -draft. So the group is skipped, and its slot is undefined.
Replace TODO with the replacement string that makes the code print 09/26/2026.
const iso = "2026-09-26";
console.log(iso.replace(/(\d{4})-(\d{2})-(\d{2})/, "TODO"));const iso = "2026-09-26";
console.log(iso.replace(/(\d{4})-(\d{2})-(\d{2})/, "$2/$3/$1"));$2/$3/$1 puts the saved month, day, and year back in a new order, with slashes between them.
Write a regex that matches exactly cat or exactly dog, and nothing longer.
const pattern = /^cat|dog$/;
console.log(pattern.test("catfish"));
console.log(pattern.test("hotdog"));const pattern = /^(?:cat|dog)$/;
console.log(pattern.test("catfish"));
console.log(pattern.test("hotdog"));/^(?:cat|dog)$/ puts both words between ^ and $. Now catfish and hotdog both give false.
What should replace TODO after .*?, so the text must end with the same quote mark it started with?
const sameQuote = /(?<quote>['"])(?<text>.*?)TODO/;
console.log(sameQuote.test("\"ok\""));
console.log(sameQuote.test("\"broken'"));const sameQuote = /(?<quote>['"])(?<text>.*?)\k<quote>/;
console.log(sameQuote.test("\"ok\""));
console.log(sameQuote.test("\"broken'"));\k<quote> asks for the same quote mark that started the text. So "broken' gives false.
Check your understanding
8 QUESTIONSSome questions ask what a word means. Others show code and ask what it prints. For code questions, first number the groups. Then work out the last line.
Question 1 of 8How do capturing groups get their numbers?
Choose an answer to see the explanation.
Question 2 of 8What does the month code print?
Read the code, then predictconst match = /(\d{4})-(\d{2})-(\d{2})/.exec("2026-09-26"); console.log(match[2]);Choose an answer to see the explanation.
Question 3 of 8What does this named-group code print?
Read the code, then predictconst match = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/.exec("2026-09-26"); const { year, month, day } = match.groups; console.log(day + "/" + month + "/" + year);Choose an answer to see the explanation.
Question 4 of 8What does a skipped optional group give?
Read the code, then predictconst match = /file-(\d+)(?:-(draft))?/.exec("file-42"); console.log(match[2]);Choose an answer to see the explanation.
Question 5 of 8Why use
(?:...)?Choose an answer to see the explanation.
Question 6 of 8What is wrong with
/^cat|dog$/if you want exactly cat or dog?Choose an answer to see the explanation.
Question 7 of 8What does the repeated-word check print?
Read the code, then predictconst repeated = /\b(?<word>[A-Za-z]+)\s+\k<word>\b/i; console.log(repeated.test("Please please stop"));Choose an answer to see the explanation.
Question 8 of 8Which sentence about using the same group name twice is true for this project?
Choose an answer to see the explanation.
Key takeaways
- Groups get numbers in the order of their opening
(.match[0]is the whole match. - A skipped group gives
undefined. A repeated group keeps only its last piece. - Named groups make match results, destructuring, and replacements easier to read.
- Use
(?:...)to group without saving and without changing the numbers. - A backreference asks for the same saved text again. The
dflag tells you where each piece starts and ends.
Remember the one-liner.
Groups save pieces of a match. Backreferences and replacements use those saved pieces again.
Up next: Anchors, boundaries & lookaround. There you match spots in the text, like the start of the text or the edge of a word. You also check the text next to a spot without making it part of the match.