This is a complete, beginner-to-advanced guide to Regular Expressions (Regex) โ a powerful mini-language for matching, searching, and manipulating text patterns. Used across nearly every programming language, regex lets you validate emails, extract data, find-and-replace text, and parse complex strings.
๐ Table of Contents
- What is a Regular Expression?
- Regex Syntax Basics
- Literal Characters and Metacharacters
- Character Classes
- Predefined Character Classes (Shorthand)
- Anchors โ Position Matching
- Quantifiers โ How Many Times?
- Greedy vs Lazy Quantifiers
- Groups and Capturing
- Alternation โ The OR Operator
- Backreferences
- Lookahead and Lookbehind
- Flags / Modifiers
- Regex in JavaScript
- Regex in Python
- Common Regex Patterns (Cheatsheet)
- Real-World Examples
- Regex Performance and Catastrophic Backtracking
- Tools for Testing Regex
- Common Mistakes and Best Practices
1๏ธโฃ What is a Regular Expression?
A regular expression (regex or regexp) is a sequence of characters that defines a search pattern. It's used to check whether a string contains a specified pattern, extract matching portions of text, or replace matched text with something else.
What Can You Do With Regex?
- โ Validate input formats โ emails, phone numbers, passwords, URLs
- ๐ Search for patterns in large text or log files
- โ๏ธ Extract specific data โ dates, prices, hashtags, IP addresses
- ๐ Replace text โ find and replace with patterns, not just exact strings
- ๐ช Split strings on complex delimiters
- ๐งน Clean and sanitize messy text data
A Simple Example
simple-example.js
// Match any string that looks like a simple email
const pattern = /^[\w.+-]+@[\w-]+\.[a-zA-Z]{2,}$/;
console.log(pattern.test("alice@example.com")); // true
console.log(pattern.test("not-an-email")); // false
console.log(pattern.test("bob@site.co")); // trueNote
2๏ธโฃ Regex Syntax Basics
Every regex is built from a combination of literal characters (that match themselves) and metacharacters (that have special meaning). Understanding which characters are special is the foundation of reading and writing regex.
How to Write a Regex
| Language | Regex Literal Syntax | Example |
|---|---|---|
| JavaScript | Slash delimiters | /pattern/flags |
| JavaScript (dynamic) | RegExp constructor | new RegExp("pattern", "flags") |
| Python | String with re module | re.compile(r"pattern") |
| Java | String with Pattern class | Pattern.compile("pattern") |
| Command line (grep) | Bare pattern | grep -E "pattern" file.txt |
The Special (Reserved) Characters
metacharacters
These 12 characters have special meaning in regex
and must be escaped with a backslash to match literally:
. ^ $ * + ? ( ) [ ] { } | \
Example: to match a literal period, you must escape it:
\. matches a literal "."
. matches ANY character (without escaping)3๏ธโฃ Literal Characters and Metacharacters
Literal Characters
Most characters in a regex simply match themselves โ letters, digits, and most punctuation.
literal-chars.js
const pattern = /cat/;
console.log(pattern.test("I have a cat")); // true
console.log(pattern.test("concatenate")); // true (matches inside the word!)
console.log(pattern.test("I have a dog")); // false
// Regex matches ANYWHERE in the string by default, not just the whole string
// Use anchors (^ and $) to match the entire string โ covered laterThe Dot (.) โ Match Any Character
dot-metachar.js
const pattern = /c.t/;
console.log(pattern.test("cat")); // true (a matches .)
console.log(pattern.test("cut")); // true (u matches .)
console.log(pattern.test("c t")); // true (space matches .)
console.log(pattern.test("ct")); // false (no character between c and t)
// To match a LITERAL dot, escape it
const literalDot = /3\.14/;
console.log(literalDot.test("3.14")); // true
console.log(literalDot.test("3x14")); // falseEscaping Special Characters
escaping.js
// To match these characters LITERALLY, escape with a backslash:
const dollarPrice = /\$\d+/; // matches "$50"
const question = /What\?/; // matches "What?"
const parens = /\(hello\)/; // matches "(hello)"
const brackets = /\[1\]/; // matches "[1]"
const pipe = /a\|b/; // matches "a|b" literally
const backslash = /C:\\Users/; // matches "C:\Users"
console.log(dollarPrice.test("Price: $50")); // true
console.log(question.test("What?")); // true4๏ธโฃ Character Classes
A character class (or character set) matches any one of the characters listed inside square brackets [...].
Basic Character Classes
char-classes.js
// [abc] matches a single 'a', 'b', or 'c'
const vowels = /[aeiou]/;
console.log(vowels.test("hello")); // true (matches 'e' or 'o')
// [0-9] matches any single digit (range notation)
const digit = /[0-9]/;
console.log(digit.test("abc123")); // true
// [a-z] matches any lowercase letter
const lowercase = /[a-z]/;
// [A-Z] matches any uppercase letter
const uppercase = /[A-Z]/;
// [a-zA-Z0-9] matches any letter or digit
const alphanumeric = /[a-zA-Z0-9]/;
// Combine ranges and literals
const hexDigit = /[0-9a-fA-F]/; // matches a single hex digit
console.log(hexDigit.test("F")); // true
console.log(hexDigit.test("G")); // falseNegated Character Classes
A caret ^ as the first character inside [...] negates the class โ it matches anything NOT in the set.
negated-classes.js
// [^abc] matches any character EXCEPT a, b, or c
const notVowel = /[^aeiou]/;
console.log(notVowel.test("xyz")); // true (x is not a vowel)
// [^0-9] matches any non-digit character
const notDigit = /[^0-9]/;
console.log(notDigit.test("abc")); // true
console.log(notDigit.test("123")); // false (all are digits)
// Practical: remove all non-alphanumeric characters
const cleaned = "Hello, World! 123".replace(/[^a-zA-Z0-9]/g, "");
console.log(cleaned); // "HelloWorld123"Special Characters Inside Character Classes
special-in-classes.js
// Inside [...], most metacharacters lose their special meaning
const pattern1 = /[.+*]/; // matches a literal ".", "+", or "*" โ no escaping needed!
// EXCEPT these still need care:
// ] must be escaped or placed first: [\]abc] or []abc]
// ^ must NOT be first (or it negates): [a^] matches 'a' or '^'
// - must be escaped or placed at start/end: [a-z] is a range, [-az] or [az-] is literal
const literalDash = /[+\-*/]/; // matches +, -, *, or / (math operators)
console.log(literalDash.test("5 + 3")); // true5๏ธโฃ Predefined Character Classes (Shorthand)
Regex provides shorthand notation for commonly used character classes so you don't have to write them out manually.
| Shorthand | Equivalent To | Matches |
|---|---|---|
| \d | [0-9] | Any digit |
| \D | [^0-9] | Any non-digit |
| \w | [a-zA-Z0-9_] | Any "word" character (letters, digits, underscore) |
| \W | [^a-zA-Z0-9_] | Any non-word character |
| \s | [ \t\n\r\f\v] | Any whitespace (space, tab, newline) |
| \S | [^ \t\n\r\f\v] | Any non-whitespace |
| . | โ | Any character except newline (unless s flag is used) |
shorthand-examples.js
// \d โ digits
console.log(/\d+/.test("Order #4521")); // true
// \w โ word characters (great for matching identifiers, usernames)
const username = /^\w+$/;
console.log(username.test("john_doe123")); // true
console.log(username.test("john doe")); // false (space is not \w)
// \s โ whitespace
const hasSpace = /\s/;
console.log(hasSpace.test("hello world")); // true
// Combine shorthand with character classes
const phoneDigits = /^\d{3}-\d{3}-\d{4}$/;
console.log(phoneDigits.test("555-123-4567")); // true
// Extract all words from a sentence
const words = "The quick brown fox".match(/\w+/g);
console.log(words); // ["The", "quick", "brown", "fox"]6๏ธโฃ Anchors โ Position Matching
Anchors don't match characters โ they match positions in the string, like the start, end, or word boundaries. They're essential for ensuring a pattern matches the whole input, not just part of it.
| Anchor | Matches |
|---|---|
| ^ | Start of the string (or line, with m flag) |
| $ | End of the string (or line, with m flag) |
| \b | Word boundary (between \w and non-\w) |
| \B | NOT a word boundary |
^ and $ โ Start and End
start-end-anchors.js
// Without anchors โ matches ANYWHERE in the string
const loose = /cat/;
console.log(loose.test("concatenate")); // true (matches inside the word)
// ^ โ must match at the START
const startsWithCat = /^cat/;
console.log(startsWithCat.test("catalog")); // true
console.log(startsWithCat.test("concat")); // false (cat is not at start)
// $ โ must match at the END
const endsWithCat = /cat$/;
console.log(endsWithCat.test("copycat")); // true
console.log(endsWithCat.test("category")); // false (cat is not at end)
// ^...$ together โ must match the ENTIRE string
const exactlyCat = /^cat$/;
console.log(exactlyCat.test("cat")); // true
console.log(exactlyCat.test("cats")); // false (extra "s")
console.log(exactlyCat.test("a cat")); // false (extra "a ")
// This is critical for validation โ without ^$ your validation is leaky!
const looseEmail = /\w+@\w+\.\w+/;
console.log(looseEmail.test("not an email but contains x@y.z inside")); // true! (BUG)
const strictEmail = /^\w+@\w+\.\w+$/;
console.log(strictEmail.test("not an email but contains x@y.z inside")); // false (correct)\b โ Word Boundary
word-boundary.js
// \b matches the position between a word character and a non-word character
// (or the start/end of the string if adjacent to a word character)
const wholeWordCat = /\bcat\b/;
console.log(wholeWordCat.test("I have a cat")); // true (cat is a whole word)
console.log(wholeWordCat.test("concatenate")); // false (cat is inside another word)
console.log(wholeWordCat.test("cats")); // false ("cat" is followed by 's')
// Without \b, "cat" would match inside "concatenate" and "cats"
const looseCat = /cat/;
console.log(looseCat.test("concatenate")); // true (incorrect for "whole word" matching)
// Practical use: find a specific word in text without matching substrings
const text = "The cat sat near the category of cats";
const matches = text.match(/\bcat\b/g);
console.log(matches); // ["cat"] โ only the standalone word, not "category" or "cats"7๏ธโฃ Quantifiers โ How Many Times?
Quantifiers specify how many times the preceding character, group, or character class should repeat. This is one of the most powerful regex concepts.
| Quantifier | Meaning | Example |
|---|---|---|
| * | 0 or more times | ab*c matches "ac", "abc", "abbbc" |
| + | 1 or more times | ab+c matches "abc", "abbc" โ NOT "ac" |
| ? | 0 or 1 time (optional) | colou?r matches "color" and "colour" |
| {n} | Exactly n times | \d{3} matches exactly 3 digits |
| {n,} | n or more times | \d{3,} matches 3+ digits |
| {n,m} | Between n and m times | \d{2,4} matches 2 to 4 digits |
Quantifier Examples
quantifiers.js
// * โ zero or more
const star = /colou*r/;
console.log(star.test("color")); // true (0 u's)
console.log(star.test("colour")); // true (1 u)
console.log(star.test("colouur")); // true (2 u's โ colouur)
// + โ one or more
const plus = /\d+/;
console.log(plus.test("abc")); // false (no digits)
console.log(plus.test("a1bc")); // true (1 digit)
console.log(plus.test("a123bc")); // true (multiple digits)
// ? โ optional (0 or 1)
const optional = /colou?r/;
console.log(optional.test("color")); // true
console.log(optional.test("colour")); // true
console.log(optional.test("colouur")); // false (2 u's not allowed)
// {n} โ exact count
const exact = /^\d{5}$/; // US ZIP code
console.log(exact.test("12345")); // true
console.log(exact.test("1234")); // false (only 4 digits)
console.log(exact.test("123456")); // false (6 digits)
// {n,} โ n or more
const atLeast = /^\d{8,}$/; // at least 8 digits
console.log(atLeast.test("12345678")); // true
console.log(atLeast.test("1234567")); // false (only 7)
// {n,m} โ range
const range = /^\d{4,6}$/; // between 4 and 6 digits
console.log(range.test("12345")); // true
console.log(range.test("123")); // false (too short)
console.log(range.test("1234567")); // false (too long)Quantifiers on Groups
quantifiers-groups.js
// Quantifiers apply to the IMMEDIATELY preceding element
// Use groups (...) to apply a quantifier to multiple characters
const repeated = /(ab)+/;
console.log(repeated.test("ababab")); // true (ab repeated 3 times)
console.log(repeated.test("ac")); // false
// Practical: match a repeating pattern like CSV-style values
const csvNumbers = /^(\d+,)*\d+$/;
console.log(csvNumbers.test("1,2,3,4")); // true
console.log(csvNumbers.test("1,2,")); // false (trailing comma)8๏ธโฃ Greedy vs Lazy Quantifiers
By default, quantifiers are greedy โ they match as much text as possible. Adding a ? after a quantifier makes it lazy (non-greedy) โ it matches as little as possible.
Greedy Matching (Default)
greedy.js
const html = "<div>Hello</div><span>World</span>";
// Greedy .* matches as MUCH as possible
const greedy = /<.+>/;
console.log(html.match(greedy)[0]);
// "<div>Hello</div><span>World</span>"
// It matched from the FIRST < to the LAST > โ likely not what you wanted!Lazy Matching (with ?)
lazy.js
const html = "<div>Hello</div><span>World</span>";
// Lazy .*? matches as LITTLE as possible
const lazy = /<.+?>/;
console.log(html.match(lazy)[0]);
// "<div>"
// It stopped at the FIRST possible match
// Get ALL tags using the global flag
const allTags = html.match(/<.+?>/g);
console.log(allTags);
// ["<div>", "</div>", "<span>", "</span>"]Greedy vs Lazy Comparison Table
| Quantifier | Type | Lazy Version |
|---|---|---|
| * | Greedy | *? |
| + | Greedy | +? |
| ? | Greedy | ?? |
| {n,m} | Greedy | {n,m}? |
Note
9๏ธโฃ Groups and Capturing
Parentheses (...) create a group. Groups serve two purposes: applying quantifiers to multiple characters, and capturing the matched text for later use.
Capturing Groups
capturing-groups.js
// Each (...) is a capturing group, numbered left to right starting at 1
const dateRegex = /(\d{4})-(\d{2})-(\d{2})/;
const match = "2024-03-15".match(dateRegex);
console.log(match[0]); // "2024-03-15" (full match)
console.log(match[1]); // "2024" (group 1 โ year)
console.log(match[2]); // "03" (group 2 โ month)
console.log(match[3]); // "15" (group 3 โ day)
// Use capturing groups in .replace() with $1, $2, etc.
const reformatted = "2024-03-15".replace(/(\d{4})-(\d{2})-(\d{2})/, "$2/$3/$1");
console.log(reformatted); // "03/15/2024"Named Capturing Groups
Use (?<name>...) to give groups meaningful names instead of numbers.
named-groups.js
const dateRegex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = "2024-03-15".match(dateRegex);
console.log(match.groups.year); // "2024"
console.log(match.groups.month); // "03"
console.log(match.groups.day); // "15"
// Use named groups in .replace()
const reformatted = "2024-03-15".replace(
/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/,
"$<month>/$<day>/$<year>"
);
console.log(reformatted); // "03/15/2024"Non-Capturing Groups
Use (?:...) when you need grouping (for a quantifier or alternation) but don't need to capture the match. This improves readability and slightly improves performance.
non-capturing-groups.js
// Capturing group โ unnecessarily captures "http" or "https"
const withCapture = /(http|https):\/\//;
const m1 = "https://example.com".match(withCapture);
console.log(m1[1]); // "https" (we may not need this)
// Non-capturing group โ groups without capturing
const withoutCapture = /(?:http|https):\/\//;
const m2 = "https://example.com".match(withoutCapture);
console.log(m2[1]); // undefined โ group wasn't captured
// Practical: group repeated patterns without polluting match results
const repeating = /(?:ab)+/;
console.log(repeating.test("ababab")); // true, but no capture array clutter๐ Alternation โ The OR Operator
The pipe | acts as a logical OR, matching either the pattern before or after it.
alternation.js
// Match "cat" OR "dog"
const petPattern = /cat|dog/;
console.log(petPattern.test("I have a cat")); // true
console.log(petPattern.test("I have a dog")); // true
console.log(petPattern.test("I have a fish")); // false
// Alternation has LOW precedence โ use groups to scope it correctly
const wrong = /gr|ay/; // matches "gr" OR "ay" โ NOT what you want
const correct = /gr(a|e)y/; // matches "gray" OR "grey"
console.log(correct.test("gray")); // true
console.log(correct.test("grey")); // true
// Multiple alternatives
const fruitPattern = /apple|banana|cherry|grape/;
console.log(fruitPattern.test("I like banana")); // true
// Combine with anchors for exact word matching
const exactDay = /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun)$/;
console.log(exactDay.test("Mon")); // true
console.log(exactDay.test("Monday")); // false (doesn't match exactly)1๏ธโฃ1๏ธโฃ Backreferences
A backreference lets you reference a previously captured group within the same pattern. This is useful for finding repeated words or matching balanced patterns like matching quote types.
backreferences.js
// \1 refers back to capturing group 1
// Find repeated consecutive words like "the the"
const repeatedWord = /\b(\w+)\s+\1\b/;
console.log(repeatedWord.test("This is is a test")); // true ("is is")
console.log(repeatedWord.test("This is a test")); // false (no repeats)
const match = "This is is a test".match(repeatedWord);
console.log(match[0]); // "is is"
console.log(match[1]); // "is"
// Match matching quote pairs (either both ' or both ")
const matchedQuotes = /(["'])(.*?)\1/;
console.log("She said 'hello'".match(matchedQuotes)[0]); // "'hello'"
console.log('She said "hello"'.match(matchedQuotes)[0]); // '"hello"'
// Named backreferences with \k<name>
const namedBackref = /(?<quote>["'])(.*?)\k<quote>/;
console.log(namedBackref.test("'single'")); // true
console.log(namedBackref.test("'mixed\"")); // false (mismatched quotes)1๏ธโฃ2๏ธโฃ Lookahead and Lookbehind
Lookaround assertions let you match a pattern only if it's followed (or preceded) by another pattern โ without including that other pattern in the match. They are zero-width assertions.
| Syntax | Name | Matches if... |
|---|---|---|
| X(?=Y) | Positive Lookahead | X is followed by Y |
| X(?!Y) | Negative Lookahead | X is NOT followed by Y |
| (?<=Y)X | Positive Lookbehind | X is preceded by Y |
| (?<!)X | Negative Lookbehind | X is NOT preceded by Y |
Positive Lookahead (?=...)
positive-lookahead.js
// Match "Tom" only if followed by " Hardy" (but don't include " Hardy" in match)
const lookahead = /Tom(?= Hardy)/;
console.log("Tom Hardy is an actor".match(lookahead)[0]); // "Tom"
console.log(lookahead.test("Tom Cruise")); // false (not followed by " Hardy")
// Practical: password validation โ must contain at least one digit
const hasDigit = /^(?=.*\d).+$/;
console.log(hasDigit.test("password123")); // true
console.log(hasDigit.test("password")); // false (no digit)
// Combine multiple lookaheads for strong password validation
const strongPassword = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*]).{8,}$/;
console.log(strongPassword.test("Passw0rd!")); // true
console.log(strongPassword.test("password")); // false (missing uppercase, digit, symbol)Negative Lookahead (?!...)
negative-lookahead.js
// Match "Tom" only if NOT followed by " Hardy"
const negLookahead = /Tom(?! Hardy)/;
console.log(negLookahead.test("Tom Cruise")); // true
console.log(negLookahead.test("Tom Hardy")); // false
// Practical: match numbers NOT followed by "px" (to find raw numbers, not CSS values)
const rawNumber = /\d+(?!px)/;
console.log("width: 100px, count: 50".match(/\d+(?!px)/g));
// Be careful โ this matches "10" inside "100px" too since lookahead checks immediately after
// For full safety combine with \b: /\b\d+\b(?!px)/Positive and Negative Lookbehind
lookbehind.js
// Positive lookbehind โ match "Hardy" only if preceded by "Tom "
const lookbehind = /(?<=Tom )Hardy/;
console.log("Tom Hardy is an actor".match(lookbehind)[0]); // "Hardy"
console.log(lookbehind.test("John Hardy")); // false
// Negative lookbehind โ match "Hardy" only if NOT preceded by "Tom "
const negLookbehind = /(?<!Tom )Hardy/;
console.log(negLookbehind.test("John Hardy")); // true
console.log(negLookbehind.test("Tom Hardy")); // false
// Practical: extract a price WITHOUT the currency symbol
const price = /(?<=\$)\d+(\.\d{2})?/;
console.log("Price: $49.99".match(price)[0]); // "49.99"
// Practical: match a number not preceded by a minus sign (positive numbers only)
const positiveOnly = /(?<!-)\b\d+\b/;
console.log("Values: -5, 10, -3, 20".match(/(?<!-)\b\d+\b/g));
// ["10", "20"] โ negative numbers excludedNote
1๏ธโฃ3๏ธโฃ Flags / Modifiers
Flags change how the regex engine processes the pattern. In JavaScript, they're added after the closing slash: /pattern/flags.
| Flag | Name | Effect |
|---|---|---|
| g | Global | Find ALL matches, not just the first |
| i | Case Insensitive | Ignore uppercase/lowercase differences |
| m | Multiline | ^ and $ match start/end of each LINE, not just the whole string |
| s | Dotall | . also matches newline characters |
| u | Unicode | Enables full Unicode matching (emoji, multi-byte chars) |
| y | Sticky | Matches only from the lastIndex position |
Flag Examples
flags.js
// g โ global (find all matches)
const text = "cat bat hat mat";
console.log(text.match(/.at/)); // ["cat"] โ only first match without 'g'
console.log(text.match(/.at/g)); // ["cat", "bat", "hat", "mat"] โ all matches
// i โ case insensitive
console.log(/hello/i.test("HELLO WORLD")); // true
console.log(/hello/.test("HELLO WORLD")); // false (case matters by default)
// m โ multiline (^ and $ match line boundaries)
const multiline = `line one
line two
line three`;
console.log(multiline.match(/^line/gm));
// ["line", "line", "line"] โ matches start of EACH line
without 'm' flag:
console.log(multiline.match(/^line/g));
// ["line"] โ only matches start of the WHOLE string
// s โ dotall (. matches newlines too)
const block = "start\nmiddle\nend";
console.log(/start.end/s.test(block)); // true (. matches \n with s flag)
console.log(/start.end/.test(block)); // false (. doesn't match \n by default)
// Combine multiple flags
const combined = /hello/gi; // global AND case-insensitive
console.log("Hello hello HELLO".match(combined));
// ["Hello", "hello", "HELLO"]1๏ธโฃ4๏ธโฃ Regex in JavaScript
Creating a Regex
creating-regex.js
// Literal notation โ preferred for static patterns
const regex1 = /hello/gi;
// Constructor notation โ needed for DYNAMIC patterns (built from variables)
const userInput = "hello";
const regex2 = new RegExp(userInput, "gi");
// Constructor with special characters needs double escaping
const dynamicPattern = new RegExp("\\d+", "g"); // matches \d+String Methods That Use Regex
| Method | Purpose | Returns |
|---|---|---|
| str.match(regex) | Find matches in a string | Array of matches, or null |
| str.matchAll(regex) | Find all matches with capture groups | Iterator of match objects |
| str.replace(regex, replacement) | Replace matched text | New string |
| str.replaceAll(regex, replacement) | Replace ALL matches (regex must have 'g' flag) | New string |
| str.split(regex) | Split string using regex delimiter | Array of substrings |
| str.search(regex) | Find the index of the first match | Number (or -1) |
| regex.test(str) | Check if a pattern exists | Boolean |
| regex.exec(str) | Find next match (stateful with 'g' flag) | Match array, or null |
Practical Method Examples
js-methods.js
// .test() โ boolean check
const hasNumber = /\d/.test("abc123"); // true
// .match() โ extract matches
const numbers = "I have 3 cats and 5 dogs".match(/\d+/g);
console.log(numbers); // ["3", "5"]
// .matchAll() โ get full match details with groups (requires 'g' flag)
const dateText = "Born 1990-05-15, died 2050-01-01";
const dateMatches = [...dateText.matchAll(/(\d{4})-(\d{2})-(\d{2})/g)];
dateMatches.forEach(m => {
console.log(`Full: ${m[0]}, Year: ${m[1]}`);
});
// Full: 1990-05-15, Year: 1990
// Full: 2050-01-01, Year: 2050
// .replace() โ find and replace with a string
const censored = "My SSN is 123-45-6789".replace(/\d{3}-\d{2}-\d{4}/, "XXX-XX-XXXX");
console.log(censored); // "My SSN is XXX-XX-XXXX"
// .replace() with a FUNCTION for dynamic replacements
const upperCased = "hello world".replace(/\b\w/g, char => char.toUpperCase());
console.log(upperCased); // "Hello World" (capitalize each word)
// .replaceAll() โ must have 'g' flag or it throws an error
const cleaned = "a-b-c-d".replaceAll(/-/g, "_");
console.log(cleaned); // "a_b_c_d"
// .split() โ split by regex pattern
const parts = "apple, banana, cherry,grape".split(/,\s*/);
console.log(parts); // ["apple", "banana", "cherry", "grape"]
// .exec() โ iterate through all matches manually
const re = /\d+/g;
let match;
while ((match = re.exec("a1 b22 c333")) !== null) {
console.log(`Found ${match[0]} at index ${match.index}`);
}
// Found 1 at index 1
// Found 22 at index 4
// Found 333 at index 81๏ธโฃ5๏ธโฃ Regex in Python
Python's re module provides regex support with a slightly different API than JavaScript.
python-regex.py
import re
# re.match() โ checks for a match at the BEGINNING of the string only
result = re.match(r"\d+", "123abc")
print(result.group()) # "123"
# re.search() โ finds the first match ANYWHERE in the string
result = re.search(r"\d+", "abc123def")
print(result.group()) # "123"
# re.findall() โ returns ALL matches as a list of strings
numbers = re.findall(r"\d+", "I have 3 cats and 5 dogs")
print(numbers) # ['3', '5']
# re.finditer() โ returns an iterator of Match objects (with position info)
for match in re.finditer(r"\d+", "I have 3 cats and 5 dogs"):
print(f"{match.group()} at position {match.start()}")
# 3 at position 7
# 5 at position 19
# re.sub() โ find and replace
result = re.sub(r"\d{3}-\d{2}-\d{4}", "XXX-XX-XXXX", "SSN: 123-45-6789")
print(result) # "SSN: XXX-XX-XXXX"
# re.sub() with a function
def capitalize(match):
return match.group().upper()
result = re.sub(r"\b\w", capitalize, "hello world")
print(result) # "Hello World"
# re.split() โ split by pattern
parts = re.split(r",\s*", "apple, banana, cherry")
print(parts) # ['apple', 'banana', 'cherry']
# Compiling a pattern for reuse (more efficient for repeated use)
pattern = re.compile(r"\d+")
print(pattern.findall("a1 b22 c333")) # ['1', '22', '333']
# Named groups
match = re.match(r"(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})", "2024-03-15")
print(match.group("year")) # "2024"
print(match.groupdict()) # {'year': '2024', 'month': '03', 'day': '15'}
# Flags in Python
re.search(r"hello", "HELLO", re.IGNORECASE) # case insensitive
re.findall(r"^line", text, re.MULTILINE) # multiline mode
re.search(r"a.b", "a\nb", re.DOTALL) # dot matches newlineRaw Strings โ Why r"..." Matters
Note
1๏ธโฃ6๏ธโฃ Common Regex Patterns (Cheatsheet)
Validation Patterns
| Use Case | Pattern |
|---|---|
| Email (simple) | ^[\w.+-]+@[\w-]+\.[a-zA-Z]{2,}$ |
| URL | ^https?:\/\/[\w.-]+\.[a-zA-Z]{2,}(\/\S*)?$ |
| US Phone Number | ^\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$ |
| IPv4 Address | ^(\d{1,3}\.){3}\d{1,3}$ |
| Hex Color Code | ^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$ |
| Username (alphanumeric + underscore, 3-16 chars) | ^[a-zA-Z0-9_]{3,16}$ |
| Strong Password (8+ chars, upper, lower, digit, symbol) | ^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_]).{8,}$ |
| Date (YYYY-MM-DD) | ^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$ |
| Time (HH:MM, 24hr) | ^([01]\d|2[0-3]):[0-5]\d$ |
| Credit Card Number (groups of 4) | ^\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}$ |
| Zip Code (US, 5 or 9 digit) | ^\d{5}(-\d{4})?$ |
| Slug (lowercase, hyphens) | ^[a-z0-9]+(?:-[a-z0-9]+)*$ |
| HTML Tag | <([a-z]+)([^<]+)*(?:>(.*)<\/\1>|\s+\/>) |
Extraction Patterns
extraction-patterns.js
// Extract all hashtags from text
const hashtags = "Loving #react and #javascript today!".match(/#\w+/g);
console.log(hashtags); // ["#react", "#javascript"]
// Extract all mentions
const mentions = "Thanks @alice and @bob!".match(/@\w+/g);
console.log(mentions); // ["@alice", "@bob"]
// Extract all URLs from text
const urls = "Visit https://example.com or http://test.org".match(/https?:\/\/[^\s]+/g);
console.log(urls); // ["https://example.com", "http://test.org"]
// Extract numbers (including decimals)
const prices = "Items cost $19.99, $5, and $120.50".match(/\d+(\.\d+)?/g);
console.log(prices); // ["19.99", "5", "120.50"]
// Extract quoted strings
const quotes = 'He said "hello" and "goodbye"'.match(/"([^"]*)"/g);
console.log(quotes); // ['"hello"', '"goodbye"']1๏ธโฃ7๏ธโฃ Real-World Examples
Email Validation (Production-Ready)
email-validation.js
function isValidEmail(email) {
const pattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
return pattern.test(email);
}
console.log(isValidEmail("user@example.com")); // true
console.log(isValidEmail("user.name+tag@sub.co")); // true
console.log(isValidEmail("invalid@")); // false
console.log(isValidEmail("@nodomain.com")); // false
// Note: For production, consider using a well-tested library
// (full RFC 5322 email validation is extremely complex)Form Input Sanitization
sanitization.js
// Remove all HTML tags from user input
function stripHtml(input) {
return input.replace(/<[^>]*>/g, "");
}
console.log(stripHtml("<p>Hello <b>World</b></p>")); // "Hello World"
// Trim and collapse multiple spaces
function normalizeWhitespace(input) {
return input.trim().replace(/\s+/g, " ");
}
console.log(normalizeWhitespace(" Hello World ")); // "Hello World"
// Remove special characters, keep only alphanumeric and spaces
function sanitizeInput(input) {
return input.replace(/[^a-zA-Z0-9\s]/g, "");
}
console.log(sanitizeInput("Hello! @World# 123")); // "Hello World 123"Parsing a Log File
log-parsing.js
const logLine = '192.168.1.1 - - [15/Mar/2024:10:30:45] "GET /api/users HTTP/1.1" 200 1024';
const logPattern = /^(?<ip>[\d.]+) - - \[(?<timestamp>[^\]]+)\] "(?<method>\w+) (?<path>[^\s]+) [^"]+" (?<status>\d+) (?<size>\d+)$/;
const match = logLine.match(logPattern);
if (match) {
console.log(match.groups);
// {
// ip: "192.168.1.1",
// timestamp: "15/Mar/2024:10:30:45",
// method: "GET",
// path: "/api/users",
// status: "200",
// size: "1024"
// }
}Converting camelCase to kebab-case
case-conversion.js
function camelToKebab(str) {
return str.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
}
console.log(camelToKebab("backgroundColor")); // "background-color"
console.log(camelToKebab("borderTopLeftRadius")); // "border-top-left-radius"
console.log(camelToKebab("myXMLParser")); // "my-xml-parser" (edge case handling needs more work)Masking Sensitive Data
masking.js
// Mask all but the last 4 digits of a credit card
function maskCardNumber(card) {
return card.replace(/\d(?=\d{4})/g, "*");
}
console.log(maskCardNumber("4111111111111234")); // "************1234"
// Mask email โ show first char and domain only
function maskEmail(email) {
return email.replace(/^(.)(.*)(?=@)/, (match, first, rest) => {
return first + "*".repeat(rest.length);
});
}
console.log(maskEmail("alice@example.com")); // "a****@example.com"1๏ธโฃ8๏ธโฃ Regex Performance and Catastrophic Backtracking
Some regex patterns can become extremely slow โ sometimes taking seconds, minutes, or even hanging indefinitely โ on certain inputs. This is called catastrophic backtracking.
What Causes Catastrophic Backtracking?
It happens when a regex has nested quantifiers that can match the same input in multiple overlapping ways. When the overall match fails, the engine tries every possible combination before giving up โ and the number of combinations grows exponentially.
catastrophic-example.js
// DANGEROUS โ nested quantifiers with overlapping possibilities
const dangerous = /^(a+)+$/;
// This input causes the regex engine to try millions of combinations
// before determining there's no match (the trailing 'b' breaks it)
const evilInput = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab";
// dangerous.test(evilInput); // This could hang the browser/server for a LONG time!
// SAFE โ rewrite without nested ambiguous quantifiers
const safe = /^a+$/;
console.log(safe.test("aaaa")); // true โ instantPatterns That Commonly Cause Issues
risky patterns
(a+)+ โ nested quantifier on the same character
(a*)* โ nested star quantifiers
(a|aa)+ โ alternation with overlapping options
(.*)* โ nested wildcards
([a-zA-Z]+)* โ repeated group with broad character classHow to Avoid Catastrophic Backtracking
- โ Avoid nesting quantifiers like (a+)+ โ simplify to a+ when possible
- โ Make alternation branches mutually exclusive, not overlapping
- โ Use atomic groups or possessive quantifiers if your engine supports them ((?>...), ++)
- โ Use specific character classes instead of .* wherever possible
- โ Test your regex against pathological inputs (long repeated characters) before deploying
- โ Set a timeout on regex execution in security-sensitive contexts (this is a common ReDoS attack vector)
Note
1๏ธโฃ9๏ธโฃ Tools for Testing Regex
Writing regex by trial and error in your code editor is slow and error-prone. Use a dedicated regex tester to build, debug, and understand patterns visually before using them in your code.
Online Regex Testers
- regex101.com โ The best all-around tool. Explains every part of your pattern, supports multiple languages (JS, Python, PHP, Java), shows step-by-step match details, and has a built-in cheat sheet.
- regexr.com โ Visual, interactive regex builder with a community pattern library and detailed explanations.
- debuggex.com โ Generates a visual railroad diagram of your regex, which is excellent for understanding complex patterns.
Regex Visualizers
Railroad diagrams turn abstract regex syntax into visual flowcharts, making it much easier to understand what a complex pattern actually does. Tools like Debuggex and regexper.com generate these automatically from any pattern you paste in.
IDE and Editor Support
- ๐๏ธ VS Code โ Built-in regex find & replace (Ctrl/Cmd + H, toggle the .* icon)
- ๐๏ธ Sublime Text โ Regex find & replace, plus regex-based multi-cursor selection
- ๐๏ธ JetBrains IDEs โ Built-in regex tester panel within find/replace dialogs
Command-Line Tools
terminal
# grep โ search files using regex
grep -E "^\d{3}-\d{4}$" phone_numbers.txt
# sed โ find and replace using regex
sed -E 's/[0-9]+/NUMBER/g' input.txt
# awk โ pattern matching and text processing
awk '/error/ {print}' server.log
# ripgrep (modern, faster grep alternative)
rg "TODO|FIXME" --type js2๏ธโฃ0๏ธโฃ Common Mistakes and Best Practices
โ Mistake 1 โ Forgetting to Escape Special Characters
mistake-1.js
// BAD โ unescaped dot matches ANY character, not just a literal period
const badDomain = /example.com/;
console.log(badDomain.test("exampleXcom")); // true! (unintended match)
// GOOD โ escape the dot
const goodDomain = /example\.com/;
console.log(goodDomain.test("exampleXcom")); // false (correct)โ Mistake 2 โ Forgetting Anchors
mistake-2.js
// BAD โ no anchors means the pattern can match ANYWHERE in the string
const badUsername = /^[a-z0-9_]{3,16}/; // missing $ at the end!
console.log(badUsername.test("validUser123<script>")); // true! (security risk)
// GOOD โ anchor both ends for strict validation
const goodUsername = /^[a-z0-9_]{3,16}$/;
console.log(goodUsername.test("validUser123<script>")); // false (correct)โ Mistake 3 โ Overusing Greedy Quantifiers
mistake-3.js
const html = "<b>Bold</b> and <i>Italic</i>";
// BAD โ greedy .* matches too much
console.log(html.match(/<.*>/)[0]);
// "<b>Bold</b> and <i>Italic</i>" โ matched everything!
// GOOD โ lazy .*? matches the minimum
console.log(html.match(/<.*?>/)[0]);
// "<b>" โ matched just the first tagโ Mistake 4 โ Using Regex for HTML/JSON Parsing
Note
mistake-4.js
// BAD โ fragile regex-based HTML parsing
const html = '<div class="card"><p>Nested <span>content</span></p></div>';
const badExtract = html.match(/<div.*?>(.*)<\/div>/);
// Breaks on nested tags, attributes with special characters, self-closing tags, etc.
// GOOD โ use a real parser
const parser = new DOMParser();
const doc = parser.parseFromString(html, "text/html");
const text = doc.querySelector(".card").textContent;
console.log(text); // "Nested content"โ Mistake 5 โ Not Testing Edge Cases
mistake-5.js
// A regex that LOOKS correct but has edge case bugs
const phonePattern = /^\d{10}$/;
console.log(phonePattern.test("1234567890")); // true
console.log(phonePattern.test("(123) 456-7890")); // false! (doesn't allow formatting)
console.log(phonePattern.test("123-456-7890")); // false! (doesn't allow dashes)
// Better โ accept common formatting variations
const flexiblePhone = /^\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$/;
console.log(flexiblePhone.test("(123) 456-7890")); // true
console.log(flexiblePhone.test("123-456-7890")); // true
console.log(flexiblePhone.test("1234567890")); // trueโ Best Practices Summary
| โ Do | โ Don't |
|---|---|
| Anchor patterns with ^ and $ for full-string validation | Forget anchors and allow partial matches to pass validation |
| Escape literal special characters: \., \$, \( | Leave metacharacters unescaped when you mean them literally |
| Use lazy quantifiers (*?, +?) for delimited content | Default to greedy quantifiers without considering over-matching |
| Use named groups for readability in complex patterns | Rely on numbered groups in long, complicated patterns |
| Test against edge cases and malicious input (ReDoS) | Ship a regex without testing pathological inputs |
| Use a real parser for HTML/XML/JSON | Try to parse nested structured data with regex |
| Comment complex regex or break it into named pieces | Write a 200-character regex with zero explanation |
| Use regex101.com to debug before shipping | Trial-and-error directly in production code |