Introduction

Welcome to the final lesson of Extracting Data with Capture Groups in JavaScript! You've come a long way through this course, building skills that let you capture structured data with named groups, enforce consistency with backreferences, and extract practical information like emails and prices from messy text. In each lesson, you've focused on finding and extracting data, which is powerful, but there's another equally important skill: transforming the data you find into a different format.

In this lesson, we'll learn to use JavaScript's String.replace() method with regular expressions to search for patterns and replace them with new text. This is far more powerful than simple string replacement because we can transform what we find rather than just replacing it wholesale. For example, we might need to standardize phone numbers from various formats into a single consistent format or redact sensitive information while preserving parts of it for context. These transformations require us to understand what we captured and intelligently modify it.

We'll start by exploring how replace() works at a basic level, then introduce numbered backreferences that let us reuse captured groups in our replacement strings. You'll see how to normalize messy phone numbers into a standard format by rearranging their captured components. Next, we'll tackle more complex scenarios where simple replacements aren't enough: you'll learn to use callback functions that execute custom logic on each match. By the end of this lesson, you'll be able to transform text patterns in sophisticated ways, completing your toolkit for both extracting and modifying data with regular expressions.

Understanding Text Transformation

Before diving into code, let's understand what makes replace() with regular expressions fundamentally different from using it with plain strings. When you call replace() with a string literal as the first argument, you specify an exact string to find and an exact string to replace it with. Every occurrence of "hello" becomes "goodbye," for instance. This works well for fixed text, but it falls apart when data varies: not all phone numbers look the same, and not all prices follow identical formatting.

The replace() method becomes much more powerful when you pass a regular expression pattern instead of a fixed string. You define what to look for using all the regex tools you've learned: character classes, quantifiers, capture groups, and anchors. The method finds matches of your pattern, then replaces each match with new text. The replacement can be a simple string, but here's where it gets interesting: you can reference the captured groups from your pattern, letting you reorganize, reformat, or selectively modify the matched content.

This capability transforms replace() from a simple find-and-replace tool into a powerful data transformation engine. Instead of just changing text, you're restructuring it. A phone number like "(415) 555-2671" contains all the information needed to create "+1-415-555-2671," but those pieces need to be rearranged and reformatted. Similarly, "alice.smith@example.com" can become "a***@example.com" by keeping the first character, hiding the rest, and preserving the domain. These aren't simple replacements; they're intelligent transformations based on what was captured.

Basic Replacement with Regular Expressions

Let's start with the simplest form of replace() using a regular expression to understand its syntax before adding complexity. The method is called on a string and takes two main arguments: the pattern to search for and the replacement text.

// Replace all sequences of whitespace with a single space
const text = "Hello    world\t\tfrom   JavaScript";
const result = text.replace(/\s+/g, ' ');
console.log(result);

Here, the pattern /\s+/g matches one or more whitespace characters: spaces, tabs, newlines, anything classified as whitespace. The g flag at the end stands for "global" and tells JavaScript to replace all matches, not just the first one. Without this flag, only the first sequence of whitespace would be replaced. The replacement string is a single space ' '. The replace() method scans through the text, finds every match of the pattern, and replaces each match with the replacement string. The effect is normalizing all whitespace to single spaces.

Hello world from JavaScript

Notice that three different types of whitespace in the original text (multiple spaces, tabs, mixed) all became single spaces in the result. This demonstrates the pattern-based nature of replace(): we didn't need to know the exact whitespace characters to replace them. The pattern matched them all, and the replacement was applied uniformly. The global flag ensured every match was replaced, not just the first. Now let's see how to incorporate captured data into our replacements.

Numbered Backreferences in Replacement Strings
Normalizing Phone Numbers with Backreferences
Testing Phone Number Normalization

Let's test our normalization function with a variety of phone number formats to verify it handles them all correctly:

const phonesText = "Call (415) 555-2671 or 415-555-8899, alt 2125550000.";
console.log(normalizePhoneNumbers(phonesText));

The test string contains three phone numbers in different formats: "(415) 555-2671" with parentheses and spaces, "415-555-8899" with hyphens, and "2125550000" with no separators at all. Our pattern must recognize all three as valid phone numbers and transform them identically.

Call +1-415-555-2671 or +1-415-555-8899, alt +1-212-555-0000.

Perfect! All three numbers have been normalized to the "+1-AAA-BBB-CCCC" format. The parentheses, spaces, and hyphens from the original text are gone, replaced by a consistent structure. Even the ten-digit string "2125550000" was correctly parsed into area code "212," prefix "555," and line number "0000." The surrounding text ("Call," "or," "alt") remained unchanged because it didn't match the pattern. This demonstrates how replace() with backreferences transforms only the matched portions while leaving everything else intact.

Callback Functions for Complex Logic

Numbered backreferences are powerful, but they have limitations: you can only rearrange and insert captured text, adding literal strings around it. What if you need to perform calculations on the captured data, apply conditional logic, or use standard JavaScript functions? For these scenarios, replace() accepts a callback function instead of a replacement string.

// Instead of a string, pass a function to replace()
const pattern = /(\d+)/g;
const result = text.replace(pattern, (match, group1) => {
  // Your transformation logic here
  return transformedString;
});

When you provide a callback function, replace() calls that function once for each match it finds. The function receives several arguments: the first is the full matched text, followed by each captured group as separate parameters. If your pattern has three capture groups, your callback receives four arguments: the full match, group 1, group 2, and group 3. The function must return a string that will replace the match. Inside your callback, you can use any JavaScript code: string methods, arithmetic, conditionals, and external function calls.

This approach is particularly useful when the transformation depends on the captured content. For instance, you might want to redact email addresses but preserve the domain and the first character of the username for context. You can't do this with a simple replacement string because you need to compute which characters to keep and which to replace with asterisks. A callback function gives you the flexibility to implement this logic in JavaScript, then return the transformed string. Let's see this in action.

Redacting Emails with a Callback
Testing Email Redaction

Let's test the redaction function with several email formats to ensure it handles different username styles correctly:

const emailsText = "Contact alice.smith@example.com and Bob-B@example.co.uk today.";
console.log(redactEmails(emailsText));

The test string contains two different email formats: "alice.smith@example.com" with a dot in the username and a standard domain, and "Bob-B@example.co.uk" with a hyphen in the username and a country-specific domain. Both should be redacted while preserving the first character and full domain.

Contact a***@example.com and B***@example.co.uk today.

Excellent! Both emails were successfully redacted. "alice.smith@example.com" became "a***@example.com," preserving the lowercase 'a' and the full domain. "Bob-B@example.co.uk" became "B***@example.co.uk," preserving the uppercase 'B' and the multi-part domain. The callback function handled each match individually, extracting the first character (whether lowercase or uppercase, letter or allowed special character) and building the appropriate redacted version. The surrounding text remained unchanged, and the domains stayed fully visible for context.

Putting It All Together

Now let's see both functions in action together, demonstrating how different transformation approaches work on different types of data:

const phonesText = "Call (415) 555-2671 or 415-555-8899, alt 2125550000.";
const emailsText = "Contact alice.smith@example.com and Bob-B@example.co.uk today.";

console.log(normalizePhoneNumbers(phonesText));
console.log(redactEmails(emailsText));

These two functions represent the two main approaches to text transformation with replace(). The phone number normalizer uses numbered backreferences: it captures three groups (area code, prefix, line number) and rearranges them with new formatting. This approach is perfect when you need to reorganize existing data into a new structure. The email redactor uses a callback function: it captures two groups (first character and domain) and applies custom logic to construct a new string. This approach is ideal when the transformation requires computation or conditional logic beyond simple rearrangement.

Call +1-415-555-2671 or +1-415-555-8899, alt +1-212-555-0000.
Contact a***@example.com and B***@example.co.uk today.

Both functions successfully transformed their respective data while leaving all other text untouched. The phone numbers now follow a consistent international format, making them easier to process, store, or display. The emails are redacted for privacy yet remain recognizable by their domains and first characters. These transformations demonstrate the practical power of replace(): you're not just finding patterns, you're intelligently modifying them to meet specific requirements. Combined with everything you've learned about capture groups, character classes, and quantifiers, you now have complete control over both extracting and transforming text data.

Conclusion and Next Steps
Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal