Slicing Through JavaScript's .replace() Method & RegEx π

Table of contents
- The Three Crust Options of .replace() π₯
- Extra Toppings: Callback Parameters π
- Regular Expression Basics π
- Capturing Groups: Separating Your Pizza Slices π
- Beyond Basic Groups: Lookaheads, Lookbehinds, and Non-Capturing Groups
- Let's Cook Up Some More Examples! π¨βπ³
- The Secret Menu: Common RegEx Patterns π
- The Perfect Recipe: Putting It All Together π§
- Wrapping It Up: The Pizza Box of Knowledge π¦

Hey coders and pizza fans! π In my last blog post, we learned about HTML lists with my top-secret, low-carb, pizza dough recipe. Today, weβre going to explore the powerful combination of JavaScript's .replace()
method and regular expressions. And because pizza makes everything better (you know I can't code without a slice nearby π), we're going to knead our way through some string manipulation examples in JavaScript.
The Three Crust Options of .replace() π₯
Just like how you can choose different types of pizza crust (thin, deep dish, or stuffed?), JavaScript gives us three different ways to use .replace()
:
// Plain Cheese: Simple String Replace
// "Pepperoni Pizza" - Simple direct replacement
"Pineapple Pizza".replace("Pineapple", "Pepperoni");
// Supreme: RegEx Replace with case-insensitive flag
// "Pepperoni pizza" - The 'i' flag ignores case differences
"pINeApPlE pizza".replace(/pineapple/i, "Pepperoni");
// Stuffed Crust: Callback Function Replace with calculation
// "Pizza with 5 toppings" - Converts to number, adds 2, then back to string
"Pizza with 3 toppings".replace(/\d+/, (match) => {
return Number(match) + 2;
});
π₯ HOT TIP: There's an important limitation to know about .replace()
that might save you debugging time:
// This works - regex pattern with callback function
"Pizza Time".replace(/Time/, () => "Party");
// This causes TypeError - string pattern doesn't support callbacks
"Pizza Time".replace("Time", () => "Party");
Extra Toppings: Callback Parameters π
When you use a callback function with .replace()
, JavaScript delivers four standard parameters:
"Extra cheesy pizza $12.99".replace(/\$(\d+\.\d+)/, function(match, group, offset, originalString) {
console.log("Full match:", match); // "$12.99" - the entire text matched by the regex
console.log("Captured group:", group); // "12.99" - just the digits captured in parentheses
console.log("Found at position:", offset); // 18 - the character position where match starts
console.log("Original order:", originalString); // "Extra cheesy pizza $12.99" - the complete string
// Apply a 25% discount to the captured price
return "$" + (Number(group) * 0.75).toFixed(2);
});
// "Extra cheesy pizza $9.74"
Let's break down each parameter:
First, the match parameter gives you the entire substring that matched your regex pattern, similar to grabbing a whole slice of pizza.
Next, the group parameter contains any content captured in parentheses from your regex, kind of like isolating just the different types of toppings on your slice. If your regex has multiple capturing groups, you'll get additional parameters (group1, group2, group3...
).
The offset parameter tells you the position where the match was found, sort of like knowing exactly which slice in the box has your favorite topping.
Finally, the originalString parameter gives you the complete string you're performing the replacement on, just like when the delivery person hands you your entire order.
Regular Expression Basics π
Regular expressions are like the pizza topping counter of JavaScript, overwhelming at first glance, but you can make some cool things happen when you learn about each ingredient.
Character Classes (The Basic Ingredients)
\d - Any digit (0-9) // Like counting pizza slices
\D - NOT a digit // Everything except the slice numbers
\w - Any word character // The letters in P-I-Z-Z-A
\W - NOT a word character // Everything that's not in P-I-Z-Z-A
\s - Any whitespace // The space between pizza and perfection
\S - NOT whitespace // Everything except those spaces
. - Any character // Whatever you want on your pizza
Quantifiers (How Much of Each Topping?)
* - 0 or more // Can match empty string or repeated characters
+ - 1 or more // Requires at least one occurrence
? - 0 or 1 (optional) // Makes the previous element optional
{3} - Exactly 3 // Matches exactly 3 occurrences
{2,4} - Between 2 and 4 // Matches between 2-4 occurrences, inclusive
Anchors (Where on the Pizza?)
^ - Start of string // Ensures pattern starts at beginning
$ - End of string // Ensures pattern goes to the very end
\b - Word boundary // Matches position between word/non-word character
Capturing Groups: Separating Your Pizza Slices π
Parentheses in regex help you grab exactly which slice you want:
const pizzaOrder = "I want a large pepperoni pizza with extra cheese";
const size = pizzaOrder.match(/a (small|medium|large) \w+ pizza/)[1];
// The [1] gets just the captured group - "large"
console.log(`Size ordered: ${size}`); // "large"
When you use parentheses ()
in a regular expression, you create "capture groups" that store matched portions separately. The .match()
method returns an array where index [0]
contains the entire match, while subsequent indexes [1]
, [2]
, etc. contain just the text matched by each set of parentheses. This lets you extract specific pieces (like "large") from a longer string without having to process the entire matched text.
Think of it as ordering a whole pizza but only wanting a particular slice - the capture group gives you just the part you need!
Beyond Basic Groups: Lookaheads, Lookbehinds, and Non-Capturing Groups
While capturing groups are powerful, sometimes you need more advanced techniques to handle complex patterns without cluttering your results.
Non-Capturing Groups: Grouping Without Storing
Non-capturing groups let you use parentheses for alternation or applying quantifiers without storing the matched content:
// Regular capturing group
const order = "Order #12345".match(/(Order #)(\d+)/);
console.log(order);
// ["Order #12345", "Order #", "12345"]
// Non-capturing group for the prefix
const orderNonCapture = "Order #12345".match(/(?:Order #)(\d+)/);
console.log(orderNonCapture);
// ["Order #12345", "12345"] - Only the order number is captured
Non-capturing groups use the syntax (?:pattern)
and are especially useful when you need to group elements for matching purposes but don't need to reference that specific match later.
Lookaheads: Checking What Follows
Lookaheads check what follows a pattern without including it in the match:
// Extract prices only when followed by "discount"
const menuText = "Margherita $10.99 standard, Pepperoni $14.99 discount";
// Positive lookahead - match prices that are followed by "discount"
const discountPrices = menuText.match(/\$\d+\.\d+(?= discount)/g);
console.log(discountPrices); // ["$14.99"]
// Negative lookahead - match prices NOT followed by "discount"
const regularPrices = menuText.match(/\$\d+\.\d+(?! discount)/g);
console.log(regularPrices); // ["$10.99"]
Lookaheads are particularly useful for validation patterns or when you need to match text only in specific contexts.
Lookbehinds: Checking What Precedes
Lookbehinds check what comes before your pattern:
const ingredients = "tomato sauce, mozzarella cheese, fresh basil, extra oregano";
// Positive lookbehind - match ingredients preceded by "extra"
const extraIngredients = ingredients.match(/(?<=extra )\w+/g);
console.log(extraIngredients); // ["oregano"]
// Negative lookbehind - match ingredients NOT preceded by "fresh" or "extra"
const standardIngredients = ingredients.match(/(?<!fresh |extra )\w+(?= \w+,|$)/g);
console.log(standardIngredients); // ["sauce", "cheese"]
Lookbehinds let you match based on what came before without including those preceding characters in your match.
Practical Example: Advanced Text Parsing
Let's see how these techniques can work together:
const customerReviews = `
ID: 1001 - Rating: 5 stars - "Best pizza in town! The crust was perfect."
ID: 1002 - Rating: 3 stars - "Delivery was slow but food was good."
ID: 1003 - Rating: 4 stars - "Great value for money. Will order again."
`;
// Extract only positive reviews (4-5 stars) with their IDs
function extractPositiveReviews(text) {
// Break down the pattern:
// (?<=ID: )(\d+) - Lookbehind for "ID: " and capture the ID number
// .+?Rating: [45] - Match text up to ratings of 4 or 5
// (?=.+?".+?") - Lookahead to ensure there's a review in quotes
const pattern = /(?<=ID: )(\d+)(.+?Rating: [45])(?=.+?"(.+?)")/g;
const matches = [];
let match;
while ((match = pattern.exec(text)) !== null) {
const reviewText = text.slice(match.index).match(/"(.+?)"/)[1];
matches.push({
id: match[1],
rating: match[2].match(/(\d+)/)[0],
review: reviewText
});
}
return matches;
}
console.log(extractPositiveReviews(customerReviews));
// [
// { id: "1001", rating: "5", review: "Best pizza in town! The crust was perfect." },
// { id: "1003", rating: "4", review: "Great value for money. Will order again." }
// ]
This example demonstrates how non-capturing groups, lookaheads, and lookbehinds can work together to extract precise information from complex text patterns. By using these advanced techniques, you can create more efficient and readable regular expressions that focus on extracting exactly the data you need.
Let's Cook Up Some More Examples! π¨βπ³
Example 1: The Pizza Order Number Incrementer
When your 99th pizza order becomes your 100th (this is a variation of the codewars kata I recently completed that inspired this blog post):
function incrementString(string) {
return string.replace(/(\d*)$/, match => {
// If no digits found at the end, add "1"
if (match === "") return "1";
// Store the original length to preserve leading zeros
const length = match.length;
// Convert to number, add 1, then back to string with padding
return (Number(match) + 1).toString().padStart(length, "0");
});
}
incrementString("Pizza"); // "Pizza1"
incrementString("Order #23"); // "Order #24"
incrementString("Receipt #099"); // "Receipt #100" - keeps leading zeros!
Example 2: Pizza Recipe Parser
Extract ingredients from recipe text:
function parseIngredients(recipe) {
const ingredients = [];
// This regex captures: amount, unit, and ingredient name
const regex = /(\d+(?:\.\d+)?) (cups?|tablespoons?|teaspoons?) of ([^,\.]+)/g;
let match;
// Loop through all matches in the text
while ((match = regex.exec(recipe)) !== null) {
ingredients.push({
amount: parseFloat(match[1]), // Convert amount to number
unit: match[2], // Get the unit (cup, tablespoon, etc)
ingredient: match[3].trim() // Get just the ingredient name, trimmed
});
}
return ingredients;
}
const pizzaSauce = "Mix 2 cups of crushed tomatoes, 1.5 tablespoons of olive oil, 2 teaspoons of oregano.";
console.log(parseIngredients(pizzaSauce));
// [{amount: 2, unit: "cups", ingredient: "crushed tomatoes"}, ...]
Example 3: Pizza Special Formatter
Format your specials menu with visual styling:
function formatSpecials(menu) {
// Convert prices to styled spans
menu = menu.replace(/\$(\d+\.\d+)/g, function(match, price, offset, originalString) {
console.log(`Found price ${price} at position ${offset} in "${originalString}"`);
return '<span class="price">$' + price + '</span>';
});
// Make pizza names bold
menu = menu.replace(/([A-Za-z]+ Pizza)/gi, '<strong>$1</strong>');
// Add pizza emoji to beginning of each line using multiline flag (m)
menu = menu.replace(/^(.+)$/gm, 'π $1');
return menu;
}
const specials = "Margherita Pizza $12.99\nPepperoni Pizza $14.99";
console.log(formatSpecials(specials));
// "π <strong>Margherita Pizza</strong> <span class="price">$12.99</span>
// π <strong>Pepperoni Pizza</strong> <span class="price">$14.99</span>"
The Secret Menu: Common RegEx Patterns π
Just like my secret pizza menu items, here are some regex patterns only the regulars know about:
// Extract all prices from a menu
const prices = "Margherita: $12.99, Pepperoni: $14.99".match(/\$\d+\.\d+/g);
// ["$12.99", "$14.99"] - the 'g' flag gets all matches!
// Split a pizza order at commas (keeping the commas)
const toppings = "Pepperoni, Mushrooms, Extra Cheese".split(/(?<=,) /);
// Uses a positive lookbehind (?<=,) to keep the commas in the result
// Validate a pizza delivery email
const isValidEmail = /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/i.test("order@pizzaplace.com");
// Returns true if the email format is valid
// Turn #hashtags into links with full callback parameter usage
const withLinks = "Love this #pizza and #code combo!".replace(/#(\w+)/g,
function(match, tag, offset, originalString) {
console.log(`Found ${match} with tag ${tag} at position ${offset}`);
console.log(`Original string: "${originalString}"`);
return '<a href="tag/' + tag + '">' + match + '</a>';
}
);
// Shows how all callback parameters can provide useful context
The Perfect Recipe: Putting It All Together π§
Just like how my perfect pizza has the exact right ratio of sauce to cheese to toppings, your RegEx skills need balance too. Let's create the ultimate pizza order parser:
function parsePizzaOrder(orderText) {
// Extract customer name - the 'm' flag enables multi-line mode
const nameMatch = orderText.match(/^Name: (.+)$/m);
const name = nameMatch ? nameMatch[1] : "Anonymous";
// Extract pizza size with case-insensitive flag
const sizeMatch = orderText.match(/Size: (Small|Medium|Large)/i);
const size = sizeMatch ? sizeMatch[1] : "Medium"; // Default size if not specified
// Extract all toppings from a single line
const toppingsMatch = orderText.match(/Toppings: (.+)$/m);
const toppings = toppingsMatch
? toppingsMatch[1].split(/,\s*/).map(t => t.trim()) // Split by comma and clean up
: ["Cheese"]; // Default topping
// Calculate price based on size and number of toppings
let basePrice = 0;
let totalPrice = 0;
// Using replace with callback function parameters for price calculation
orderText.replace(/Size: (Small|Medium|Large)/i, function(match, sizeGroup, offset, original) {
console.log(`Found size "${sizeGroup}" at position ${offset}`);
basePrice = sizeGroup.toLowerCase() === "small" ? 8 :
sizeGroup.toLowerCase() === "medium" ? 10 : 12;
const toppingPrice = Math.max(0, toppings.length - 1) * 2; // First topping free!
totalPrice = basePrice + toppingPrice;
return match; // Keep the original text (we're just using replace for the callback)
});
// If no size was found, set default prices
if (basePrice === 0) {
basePrice = 10; // Default Medium price
const toppingPrice = Math.max(0, toppings.length - 1) * 2;
totalPrice = basePrice + toppingPrice;
}
return {
customer: name,
order: { size, toppings },
price: `$${totalPrice.toFixed(2)}`
};
}
const order = `
Name: Samir
Size: Large
Toppings: Pepperoni, Mushrooms, Bell Peppers
`;
console.log(parsePizzaOrder(order));
// {
// customer: "Samir",
// order: {
// size: "Large",
// toppings: ["Pepperoni", "Mushrooms", "Bell Peppers"]
// },
// price: "$16.00"
// }
Wrapping It Up: The Pizza Box of Knowledge π¦
And there you have it, folks! We've sliced through JavaScript's .replace()
method and regular expressions like a pizza cutter through a perfectly baked pie. Remember:
.replace()
comes in three flavors: simple strings, regex patterns, and callback functionsCallbacks get four bonus parameters. First is match (the full text matched by your regex). Next comes group(s) (the content captured in parentheses). Third is offset (the position where the match was found). Finally, originalString gives you the complete string you're working with.
Regular expressions are weird looking but super powerful - just like experimenting with unique pizza combinations
Capturing groups let you grab specific pieces of your text - just like picking out your favorite ingredients
Practice makes perfect, whether it's kneading dough or writing regex.
Are you team regex or team "just use string methods"? And what's your favorite pizza topping combination? Drop a comment!
Until next time...ππβοΈ
P.S. Follow me on BlueSky, X/Twitter, or check out my previous post about HTML lists and CSS styling - I even give out my secret pizza low-carb pizza recipe! π€«
Subscribe to my newsletter
Read articles from Samir Shuman directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Samir Shuman
Samir Shuman
Welcome to my blog! My name is Samir. I'm a full-stack software engineer specializing in web development. I'm based in the San Francisco Bay Area, but can deliver digital solutions globally. When I'm not coding, I love experimenting in the kitchen, watching basketball, and spending time with my cat, Fuzzy. Let's connect!