Regular Expressions in JavaScript

· 6 min read · Updated March 11, 2026 · intermediate
regex regular-expressions string validation patterns

Regular expressions (regex) are patterns used to match character combinations in strings. In JavaScript, they’re a powerful tool for validation, search, and text manipulation. If you’ve ever needed to check if an email looks real, extract URLs from text, or find all the numbers in a string, regex is the answer.

Creating a Regular Expression

You can create a regex in two ways. The literal syntax is most common:

const pattern = /hello/;

Or you can use the RegExp constructor when the pattern comes from a variable:

const searchTerm = "hello";
const pattern = new RegExp(searchTerm);

Both produce a RegExp object. The literal syntax is cleaner for fixed patterns.

Basic Matching

At its simplest, a regex matches exact characters:

const regex = /hello/;
console.log(regex.test("hello world")); // true
console.log(regex.test("goodbye"));    // false

The dot . matches any single character:

const regex = /c.t/;
console.log(regex.test("cat"));   // true
console.log(regex.test("cut"));   // true
console.log(regex.test("cart"));  // false

Character Classes

Square brackets let you match specific sets of characters:

// Match any vowel
const vowelRegex = /[aeiou]/;
console.log(vowelRegex.test("bcd")); // false
console.log(vowelRegex.test("axe"));  // true

// Match any digit
const digitRegex = /[0-9]/;
console.log(digitRegex.test("year2024")); // true

// Negate with ^
const notDigit = /[^0-9]/;
console.log(notDigit.test("abc")); // true
console.log(notDigit.test("123")); // false

Predefined character classes make common patterns shorter:

\w  // Word character: [a-zA-Z0-9_]
\W  // Non-word character
\d  // Digit: [0-9]
\D  // Non-digit
\s  // Whitespace (space, tab, newline)
\S  // Non-whitespace
.   // Any character except newline
const hasDigit = /\d/;
const isWord = /\w+/;
console.log(hasDigit.test("hello"));    // false
console.log(isWord.test("hello_123")); // true

Anchors

Anchors don’t match characters — they match positions in the string:

^   // Start of string
$   // End of string
\b  // Word boundary
const startsWithNum = /^\d/;
console.log(startsWithNum.test("123abc")); // true
console.log(startsWithNum.test("abc123")); // false

const endsWithNum = /\d$/;
console.log(endsWithNum.test("abc123")); // true
console.log(endsWithNum.test("123abc")); // false

const wholeWord = /\bhello\b/;
console.log(wholeWord.test("hello world"));  // true
console.log(wholeWord.test("helloworld"));   // false

Quantifiers

Quantifiers specify how many times something should match:

*   // 0 or more
+   // 1 or more
?   // 0 or 1 (optional)
{n} // Exactly n times
{n,} // n or more times
{n,m} // Between n and m times
// Match one or more digits
const digits = /\d+/;
console.log(digits.test("a123b")); // true
console.log(digits.test("abc"));   // false

// Match optional 's' at end
const plural = /dogs?/;
console.log(plural.test("dog"));  // true
console.log(plural.test("dogs")); // true

// Match 3-5 letter word
const threeToFive = /\b\w{3,5}\b/;
console.log(threeToFive.test("hi"));      // false
console.log(threeToFive.test("cat"));     // true
console.log(threeToFive.test("elephant")); // false

Greedy vs lazy matching matters. By default, quantifiers are greedy — they match as much as possible:

const greedy = /".*"/;    // Greedy
const lazy = /".*?"/;     // Lazy (add ?)

const text = '"hello" "world"';
console.log(text.match(greedy)); // ['"hello" "world"']
console.log(text.match(lazy));   // ['"hello"', '"world"']

Groups and Backreferences

Parentheses create capturing groups, which let you extract parts of a match:

const dateRegex = /(\d{4})-(\d{2})-(\d{2})/;
const match = "2024-03-11".match(dateRegex);

console.log(match[0]); // "2024-03-11" (full match)
console.log(match[1]); // "2024" (first group)
console.log(match[2]); // "03" (second group)
console.log(match[3]); // "11" (third group)

You can reference groups later with \1, \2, etc:

// Match repeated words
const repeatedWord = /\b(\w+)\s\1\b/;
console.log(repeatedWord.test("the the")); // true
console.log(repeatedWord.test("the cat")); // false

Non-capturing groups use (?:...) when you need grouping but don’t want to capture:

const regex = /(?:Mr|Mrs|Ms)\. (\w+)/;
const match = "Mr. Smith".match(regex);
console.log(match[1]); // "Smith"

Flags

Flags modify how the regex behaves:

g   // Global — find all matches, not just first
i   // Case-insensitive
m   // Multiline — ^ and $ match line starts/ends
s   // Dotall — . matches newlines
u   // Unicode — enables Unicode features
y   // Sticky — match at lastIndex position only
// Case insensitive
const caseInsensitive = /hello/i;
console.log(caseInsensitive.test("HELLO")); // true
console.log(caseInsensitive.test("Hello")); // true

// Global - find all matches
const allDigits = /\d/g;
console.log("abc123def456".match(allDigits)); // ['1', '2', '3', '4', '5', '6']

// Multiline
const multiLine = /^line/m;
console.log("line1\nline2\nline".match(multiLine)); // ['line', 'line']

The u flag is essential for Unicode matching and enables Unicode property escapes:

const emojiRegex = /\p{Emoji}/u;
console.log(emojiRegex.test("😀")); // true
console.log(emojiRegex.test("a"));  // false

Working with Regex in JavaScript

RegExp Methods

The test() method returns true or false:

const hasUpper = /[A-Z]/;
console.log(hasUpper.test("hello")); // false
console.log(hasUpper.test("Hello")); // true

The exec() method returns match details or null:

const regex = /(\d{4})-(\d{2})-(\d{2})/;
const result = regex.exec("Date: 2024-03-11");

if (result) {
  console.log(result[0]);    // "2024-03-11"
  console.log(result.index); // 6
}

String Methods

The match() method returns an array of matches:

const str = "I have 3 cats and 5 dogs";
const numbers = str.match(/\d+/g);
console.log(numbers); // ['3', '5']

The replace() and replaceAll() methods let you substitute text:

// Replace first occurrence
const result1 = "hello world".replace(/o/, "x");
console.log(result1); // "hellx world"

// Replace all with global flag
const result2 = "hello world".replace(/o/g, "x");
console.log(result2); // "hellx wxrld"

// Use captured groups in replacement
const result3 = "2024-03-11".replace(/(\d{4})-(\d{2})-(\d{2})/, "$3/$2/$1");
console.log(result3); // "11/03/2024"

// Replacement function
const result4 = "abc123def".replace(/\d+/g, (match) => parseInt(match) * 2);
console.log(result4); // "abc246def"

The split() method uses regex as delimiter:

console.log("a,b;c:d".split(/[,;:/]/));          // ['a', 'b', 'c', 'd']
console.log("hello   world".split(/\s+/));       // ['hello', 'world']

The search() method returns the index of the match:

console.log("hello world".search(/wor/));  // 6
console.log("hello world".search(/foo/)); // -1 (not found)

The matchAll() method returns an iterator of all matches with groups:

const regex = /(\d+)/g;
const str = "numbers: 1, 2, 3";
const matches = [...str.matchAll(regex)];

console.log(matches[0]); // ['1', '1', index: 9, ...]
console.log(matches[1]); // ['2', '2', index: 12, ...]
console.log(matches[2]); // ['3', '3', index: 15, ...]

Practical Examples

Email Validation

function isValidEmail(email) {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return emailRegex.test(email);
}

console.log(isValidEmail("test@example.com"));   // true
console.log(isValidEmail("invalid@"));           // false
console.log(isValidEmail("no@space here.com")); // false

Extracting URLs

const text = "Visit https://example.com or http://test.org for more info";
const urlRegex = /https?:\/\/[^\s]+/g;
const urls = text.match(urlRegex);
console.log(urls); // ['https://example.com', 'http://test.org']

Phone Number Formatting

function formatPhone(phone) {
  const digits = phone.replace(/\D/g, "");
  const match = digits.match(/^(\d{3})(\d{3})(\d{4})$/);
  if (match) {
    return `(${match[1]}) ${match[2]}-${match[3]}`;
  }
  return phone;
}

console.log(formatPhone("5551234567"));   // "(555) 123-4567"
console.log(formatPhone("555-123-4567")); // "(555) 123-4567"
console.log(formatPhone("5551234"));      // "5551234" (invalid)

Password Strength

function isStrongPassword(password) {
  const hasUpper = /[A-Z]/.test(password);
  const hasLower = /[a-z]/.test(password);
  const hasDigit = /\d/.test(password);
  const hasSpecial = /[!@#$%^&*]/.test(password);
  const longEnough = password.length >= 8;
  
  const score = [hasUpper, hasLower, hasDigit, hasSpecial, longEnough]
    .filter(Boolean).length;
  
  return score >= 4;
}

console.log(isStrongPassword("abc123"));      // false
console.log(isStrongPassword("Abc123!"));     // true
console.log(isStrongPassword("SecureP@ss1")); // true

Common Pitfalls

Escaping Special Characters

Some characters have special meaning and need escaping:

// Match a literal dot
const dot = /\./;
console.log(dot.test("hello.world")); // true

// Match literal brackets
const brackets = /\[.*\]/;
console.log(brackets.test("[test]")); // true

Performance

Complex patterns can cause performance issues with catastrophic backtracking:

// Problematic: nested quantifiers can cause exponential backtracking
const badPattern = /(a+)+b/;

// Better: simpler pattern
const goodPattern = /a+b/;

Lookahead and Lookbehind

Lookaheads check what comes next without including it in the match:

// Positive lookahead: must be followed by "world"
const regex = /hello(?= world)/;
console.log(regex.test("hello world")); // true
console.log(regex.test("hello there")); // false

// Negative lookahead: must NOT be followed by "world"
const regex2 = /hello(?! world)/;
console.log(regex2.test("hello world")); // false
console.log(regex2.test("hello there")); // true

Lookbehind (ES2018+) checks what comes before:

// Positive lookbehind: must be preceded by "$"
const regex = /(?<=\$)\d+/;
console.log(regex.exec("price is $100")); // ["100"]

// Negative lookbehind: must NOT be preceded by "$"
const regex2 = /(?<!\$)\d+/;
console.log(regex2.test("$100")); // false
console.log(regex2.test("a100")); // true

See Also