๐Ÿงฉ Regular Expressions โ€” Complete Tutorial

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.

>>Regex is a language for describing patterns in text. Master it, and you can search, validate, and transform almost anything.

๐Ÿ“– Table of Contents

  1. What is a Regular Expression?
  2. Regex Syntax Basics
  3. Literal Characters and Metacharacters
  4. Character Classes
  5. Predefined Character Classes (Shorthand)
  6. Anchors โ€” Position Matching
  7. Quantifiers โ€” How Many Times?
  8. Greedy vs Lazy Quantifiers
  9. Groups and Capturing
  10. Alternation โ€” The OR Operator
  11. Backreferences
  12. Lookahead and Lookbehind
  13. Flags / Modifiers
  14. Regex in JavaScript
  15. Regex in Python
  16. Common Regex Patterns (Cheatsheet)
  17. Real-World Examples
  18. Regex Performance and Catastrophic Backtracking
  19. Tools for Testing Regex
  20. 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"));         // true

Note

๐Ÿ“Œ Regex is supported natively in almost every programming language: JavaScript, Python, Java, PHP, Ruby, Go, C#, and as a standalone tool in grep, sed, and text editors like VS Code and Sublime Text.

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

LanguageRegex Literal SyntaxExample
JavaScriptSlash delimiters/pattern/flags
JavaScript (dynamic)RegExp constructornew RegExp("pattern", "flags")
PythonString with re modulere.compile(r"pattern")
JavaString with Pattern classPattern.compile("pattern")
Command line (grep)Bare patterngrep -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 later

The 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"));  // false

Escaping 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?"));          // true

4๏ธโƒฃ 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"));     // false

Negated 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"));  // true

5๏ธโƒฃ Predefined Character Classes (Shorthand)

Regex provides shorthand notation for commonly used character classes so you don't have to write them out manually.

ShorthandEquivalent ToMatches
\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.

AnchorMatches
^Start of the string (or line, with m flag)
$End of the string (or line, with m flag)
\bWord boundary (between \w and non-\w)
\BNOT 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.

QuantifierMeaningExample
*0 or more timesab*c matches "ac", "abc", "abbbc"
+1 or more timesab+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

QuantifierTypeLazy Version
*Greedy*?
+Greedy+?
?Greedy??
{n,m}Greedy{n,m}?

Note

๐Ÿ’ก Rule of thumb: When matching delimited content like HTML tags, quoted strings, or parentheses, lazy quantifiers usually give the result you actually want. Greedy quantifiers are correct when you genuinely want the longest possible match (e.g., matching an entire block of digits).

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.

SyntaxNameMatches if...
X(?=Y)Positive LookaheadX is followed by Y
X(?!Y)Negative LookaheadX is NOT followed by Y
(?<=Y)XPositive LookbehindX is preceded by Y
(?<!)XNegative LookbehindX 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 excluded

Note

โš ๏ธ Lookbehind support varies by language/engine. JavaScript supports it since ES2018 (modern browsers and Node.js). Some older regex engines (like older versions of Python's re for variable-length lookbehind) have limitations.

1๏ธโƒฃ3๏ธโƒฃ Flags / Modifiers

Flags change how the regex engine processes the pattern. In JavaScript, they're added after the closing slash: /pattern/flags.

FlagNameEffect
gGlobalFind ALL matches, not just the first
iCase InsensitiveIgnore uppercase/lowercase differences
mMultiline^ and $ match start/end of each LINE, not just the whole string
sDotall. also matches newline characters
uUnicodeEnables full Unicode matching (emoji, multi-byte chars)
yStickyMatches 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

MethodPurposeReturns
str.match(regex)Find matches in a stringArray of matches, or null
str.matchAll(regex)Find all matches with capture groupsIterator of match objects
str.replace(regex, replacement)Replace matched textNew string
str.replaceAll(regex, replacement)Replace ALL matches (regex must have 'g' flag)New string
str.split(regex)Split string using regex delimiterArray of substrings
str.search(regex)Find the index of the first matchNumber (or -1)
regex.test(str)Check if a pattern existsBoolean
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 8

1๏ธโƒฃ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 newline

Raw Strings โ€” Why r"..." Matters

Note

๐Ÿ“Œ Always use Python's raw strings (r"...") for regex patterns. Without the r prefix, Python interprets \d as an escape sequence before regex even sees it, causing bugs. r"\d+" is correct; "\d+" can behave unpredictably.

1๏ธโƒฃ6๏ธโƒฃ Common Regex Patterns (Cheatsheet)

Validation Patterns

Use CasePattern
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 โ€” instant

Patterns 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 class

How 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

โš ๏ธ ReDoS (Regular Expression Denial of Service) is a real security vulnerability. If your regex processes user input and has nested quantifiers, an attacker can craft input that hangs your server. Always test regex patterns against malicious-looking repeated input.

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 js

2๏ธโƒฃ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

โš ๏ธ Regex is NOT suitable for parsing structured, nested formats like HTML, XML, or JSON. These formats can have arbitrary nesting that regex (a non-recursive pattern matcher) cannot reliably handle. Use a proper parser: DOMParser or a library like Cheerio for HTML, JSON.parse() for JSON.

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 validationForget 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 contentDefault to greedy quantifiers without considering over-matching
Use named groups for readability in complex patternsRely 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/JSONTry to parse nested structured data with regex
Comment complex regex or break it into named piecesWrite a 200-character regex with zero explanation
Use regex101.com to debug before shippingTrial-and-error directly in production code
>>Regex is a write-once, read-never language โ€” until you have to debug it six months later. Comment generously, test thoroughly, and keep patterns as simple as the problem allows. ๐Ÿงฉ

Note

๐Ÿ“Œ What to Learn Next: Explore regex engines (NFA vs DFA) to understand performance characteristics, the Unicode property escapes (\p{Emoji}, \p{Letter}) for internationalized text matching, and dedicated parser libraries (like PEG.js or ANTLR) for cases where regex truly isn't the right tool.