JavaScriptAdvanced

JavaScript Regular Expressions: Syntax, Groups, Patterns

Learn JavaScript regular expressions step by step: character classes, quantifiers, groups, anchors, flags, lookaheads and ready-to-use patterns.

All JavaScript lessons

What you will learn

Imagine you must check if a phone number has 10 digits, find every hashtag in a post, or remove extra spaces from text. With plain string methods this takes long, messy code. Regular expressions (regex or RegExp) let you describe a pattern and let JavaScript search for it. In this lesson you will learn how to write patterns with character classes, quantifiers, anchors, groups and flags, how to use them with test, match, matchAll, replace and split, and many patterns you can use in real projects.

While you learn, you can try every pattern live in the free Regex Tester tool. It highlights the matches as you type, which is the fastest way to understand regex.

What is a regular expression?

A regular expression is a pattern that describes a set of strings. In JavaScript it is its own type of object.

Two ways to create one

// 1. Literal form: between slashes (most common)
const pattern1 = /hello/;

// 2. Constructor form: from a string (use when the pattern is built at runtime)
const pattern2 = new RegExp("hello");

console.log(pattern1.test("hello world"));   // true
console.log(pattern2.test("hello world"));   // true

With the constructor, a backslash must be written twice because it is inside a string:

const a = /\d+/;
const b = new RegExp("\\d+");   // same pattern

The simplest pattern: plain text

A pattern made of normal letters matches exactly that text.

console.log(/cat/.test("concatenate"));   // true (cat is inside)
console.log(/cat/.test("dog"));           // false
console.log(/Cat/.test("cat"));           // false (case matters)

The real power comes from special characters. Let us learn them one group at a time.

Character classes

A character class matches one character from a set.

Square brackets [...]

console.log(/[aeiou]/.test("sky"));        // false (no vowel)
console.log(/[aeiou]/.test("hello"));      // true

console.log("cat bat rat".match(/[cbr]at/g));   // ["cat", "bat", "rat"]

Use a range with a dash:

/[a-z]/      // any small letter
/[A-Z]/      // any capital letter
/[0-9]/      // any digit
/[a-zA-Z0-9]/   // any letter or digit

Use ^ at the start to mean NOT:

console.log("a1b2c3".match(/[^0-9]/g));   // ["a", "b", "c"] (everything except digits)

Shortcuts

Shortcut Meaning Same as
\d A digit [0-9]
\D Not a digit [^0-9]
\w A “word” character: letter, digit or underscore [A-Za-z0-9_]
\W Not a word character [^A-Za-z0-9_]
\s Whitespace: space, tab, new line
\S Not whitespace
. Any character except a new line
console.log("Room 42, Floor 7".match(/\d/g));    // ["4", "2", "7"]
console.log("Room 42, Floor 7".match(/\d+/g));   // ["42", "7"]
console.log("a b\tc".split(/\s/));               // ["a", "b", "c"]
console.log(/c.t/.test("cat"));                  // true
console.log(/c.t/.test("c9t"));                  // true

Escaping special characters

Some characters have a special meaning: . * + ? ^ $ { } ( ) | [ ] \ /. To match them as plain text, put a backslash before them:

console.log(/3.14/.test("3x14"));     // true  (the dot matched "x")
console.log(/3\.14/.test("3x14"));    // false
console.log(/3\.14/.test("3.14"));    // true

console.log(/\(\d+\)/.test("Call (100)"));   // true

Quantifiers: how many times?

A quantifier says how many times the thing before it can repeat.

Quantifier Meaning
* 0 or more times
+ 1 or more times
? 0 or 1 time (optional)
{3} Exactly 3 times
{2,5} Between 2 and 5 times
{3,} 3 or more times
console.log(/colou?r/.test("color"));     // true
console.log(/colou?r/.test("colour"));    // true

console.log("a aa aaa".match(/a+/g));     // ["a", "aa", "aaa"]

console.log(/^\d{6}$/.test("110001"));    // true  (exactly 6 digits)
console.log(/^\d{6}$/.test("1100"));      // false

console.log("12 345 6789".match(/\d{2,3}/g));   // ["12", "345", "678"]

Greedy vs lazy

Quantifiers are greedy by default: they take as much as possible. Add ? after them to make them lazy: take as little as possible.

const html = "<b>one</b> and <b>two</b>";

console.log(html.match(/<b>.*<\/b>/)[0]);    // "<b>one</b> and <b>two</b>"  (greedy, too much)
console.log(html.match(/<b>.*?<\/b>/)[0]);   // "<b>one</b>"                 (lazy, just right)

Anchors and boundaries

Anchors do not match a character. They match a position.

Anchor Matches
^ Start of the text (or of each line with the m flag)
$ End of the text (or of each line with the m flag)
\b A word boundary (between a word character and a non-word character)
\B Not a word boundary
console.log(/^hello/.test("hello world"));   // true
console.log(/^world/.test("hello world"));   // false
console.log(/world$/.test("hello world"));   // true

The most important idea: to check that the whole text matches a pattern, use ^ and $ together.

console.log(/\d+/.test("abc123xyz"));      // true  (a digit exists somewhere)
console.log(/^\d+$/.test("abc123xyz"));    // false (the whole text is not digits)
console.log(/^\d+$/.test("123"));          // true

Word boundary:

console.log(/\bcat\b/.test("a cat sat"));    // true
console.log(/\bcat\b/.test("concatenate"));  // false (cat is inside another word)

Alternation: OR with |

console.log(/cat|dog/.test("I have a dog"));   // true
console.log("red green blue".match(/red|blue/g));   // ["red", "blue"]

console.log(/^(jpg|png|gif)$/.test("png"));    // true
console.log(/^(jpg|png|gif)$/.test("pdf"));    // false

Groups

Round brackets ( ) do three jobs: they group parts, they capture the matched text, and they let a quantifier apply to several characters.

Grouping for a quantifier

console.log(/^(ha)+$/.test("hahaha"));   // true
console.log(/^(ha)+$/.test("hahah"));    // false

Capturing groups

const match = "2026-10-02".match(/(\d{4})-(\d{2})-(\d{2})/);

console.log(match[0]);   // "2026-10-02" (the whole match)
console.log(match[1]);   // "2026"       (group 1)
console.log(match[2]);   // "10"         (group 2)
console.log(match[3]);   // "02"         (group 3)

Named groups

Names make the code much easier to read:

const match = "2026-10-02".match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/);

console.log(match.groups.year);    // "2026"
console.log(match.groups.month);   // "10"

const { year, month, day } = match.groups;
console.log(`${day}/${month}/${year}`);   // "02/10/2026"

Non-capturing groups (?:...)

Use them when you only need grouping and not the captured value:

console.log("http://a.com https://b.com".match(/https?:\/\/(?:www\.)?\w+\.com/g));
// ["http://a.com", "https://b.com"]

Backreferences

\1 repeats whatever group 1 matched. This finds repeated words:

console.log(/\b(\w+) \1\b/.test("this is is a test"));   // true (is is)
console.log(/\b(\w+) \1\b/.test("this is a test"));      // false

Flags

Flags go after the closing slash and change how the pattern works.

Flag Name Effect
g global Find all matches, not only the first
i ignore case A and a are the same
m multiline ^ and $ match at each line
s dotAll . also matches a new line
u unicode Correct handling of emoji and other Unicode characters
y sticky Match only at the exact current position
d indices Include the start and end positions of matches
console.log(/hello/i.test("HELLO"));            // true
console.log("a1b2c3".match(/\d/g));             // ["1", "2", "3"]

const text = "line one\nline two";
console.log(text.match(/^line/gm));             // ["line", "line"]

You can combine flags: /pattern/gi. With the constructor, pass them as the second argument: new RegExp("pattern", "gi").

Lookahead and lookbehind

These check what is before or after a position, without including it in the match.

Syntax Name Meaning
X(?=Y) Positive lookahead X followed by Y
X(?!Y) Negative lookahead X not followed by Y
(?<=Y)X Positive lookbehind X preceded by Y
(?<!Y)X Negative lookbehind X not preceded by Y
// Numbers followed by "px"
console.log("10px 20em 30px".match(/\d+(?=px)/g));    // ["10", "30"]

// Numbers NOT followed by "px"
console.log("10px 20em 30px".match(/\d+(?!px|\d)/g)); // ["20"]

// The price after a dollar sign
console.log("cost: $50 or Rs 40".match(/(?<=\$)\d+/g));   // ["50"]

Methods that use regex

Regex objects have methods, and many string methods accept a regex.

regex.test(string): yes or no

console.log(/^\d+$/.test("12345"));   // true

string.match(regex): find matches

Without the g flag, match returns the first match with its groups (or null). With g, it returns all matches as an array of texts (or null).

console.log("a1b22c333".match(/\d+/));    // ["1", index: 1, ...] first match with details
console.log("a1b22c333".match(/\d+/g));   // ["1", "22", "333"]
console.log("abc".match(/\d+/g));         // null (no match)

Because match can return null, protect your code:

const numbers = "abc".match(/\d+/g) ?? [];
console.log(numbers.length);   // 0

string.matchAll(regex): all matches with groups

matchAll needs the g flag and gives you every match with its groups:

const text = "riya@x.com, karan@y.com";

for (const m of text.matchAll(/(?<user>\w+)@(?<site>\w+\.com)/g)) {
  console.log(m.groups.user, m.groups.site);
}
// riya x.com
// karan y.com

string.replace(regex, replacement) and replaceAll

Without g, only the first match is replaced:

console.log("a-b-c".replace(/-/, "+"));    // "a+b-c"
console.log("a-b-c".replace(/-/g, "+"));   // "a+b+c"
console.log("a-b-c".replaceAll(/-/g, "+")); // "a+b+c" (replaceAll with regex needs g)

Use captured groups in the replacement with $1, $2, or $<name>:

console.log("Riya Sharma".replace(/(\w+) (\w+)/, "$2, $1"));   // "Sharma, Riya"

console.log("2026-10-02".replace(/(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})/, "$<d>/$<m>/$<y>"));
// "02/10/2026"

Use a function for full control:

console.log("price 10 and 20".replace(/\d+/g, (n) => n * 2));   // "price 20 and 40"

console.log("hello big world".replace(/\b\w/g, (letter) => letter.toUpperCase()));
// "Hello Big World"

string.split(regex)

console.log("a, b;c  d".split(/[,; ]+/));   // ["a", "b", "c", "d"]
console.log("one1two22three".split(/\d+/)); // ["one", "two", "three"]

string.search(regex): position of the first match

console.log("hello 42".search(/\d/));   // 6
console.log("hello".search(/\d/));      // -1

regex.exec(string)

exec returns the match details one match at a time. In modern code, matchAll is easier, but you may see exec in older code.

const re = /\d+/g;
console.log(re.exec("a12b345")[0]);   // "12"
console.log(re.exec("a12b345")[0]);   // "345" (it remembers where it stopped)

Ready-to-use patterns

Always test these patterns in the Regex Tester with good and bad examples before using them.

// Indian mobile number (10 digits, starts with 6-9)
const mobile = /^[6-9]\d{9}$/;
console.log(mobile.test("9876543210"));   // true
console.log(mobile.test("1234567890"));   // false

// Indian PIN code (6 digits, cannot start with 0)
const pin = /^[1-9]\d{5}$/;
console.log(pin.test("110001"));   // true

// A simple email check (not perfect, see the note below)
const email = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
console.log(email.test("riya@example.com"));   // true
console.log(email.test("riya@example"));       // false

// Hex colour: #fff or #ffffff
const hex = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
console.log(hex.test("#FF8800"));   // true
console.log(hex.test("#GG0000"));   // false

// Username: 3 to 16 letters, digits or underscore
const username = /^\w{3,16}$/;
console.log(username.test("riya_22"));   // true
console.log(username.test("ab"));        // false

// Date as DD/MM/YYYY (checks the shape only, not if the date is real)
const dateShape = /^\d{2}\/\d{2}\/\d{4}$/;
console.log(dateShape.test("02/10/2026"));   // true

Note on emails: the perfect email regex does not exist. A simple pattern like the one above catches obvious typing mistakes. The only real proof that an email works is to send a message to it.

A strong password check with lookaheads

// At least 8 characters, one lowercase, one uppercase, one digit
const strong = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/;

console.log(strong.test("Hello123"));   // true
console.log(strong.test("hello123"));   // false (no capital)
console.log(strong.test("Hi1"));        // false (too short)

Real-life use cases

1. Make a URL slug from a title

function slugify(title) {
  return title
    .toLowerCase()
    .trim()
    .replace(/[^a-z0-9]+/g, "-")   // anything not a letter or digit becomes a dash
    .replace(/^-+|-+$/g, "");      // remove dashes at the start and the end
}

console.log(slugify("  JavaScript Regex: A Beginner's Guide!  "));
// "javascript-regex-a-beginner-s-guide"

2. Format a number with commas

function addCommas(number) {
  return String(number).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}

console.log(addCommas(1234567));   // "1,234,567"
console.log(addCommas(999));       // "999"

(For real money formatting you can also use toLocaleString.)

3. Find all hashtags and mentions

const post = "Learning #javascript with @mycodenest and #regex";

console.log(post.match(/#\w+/g));   // ["#javascript", "#regex"]
console.log(post.match(/@\w+/g));   // ["@mycodenest"]

4. Clean messy spaces

const messy = "   too    many     spaces   here   ";

console.log(messy.trim().replace(/\s+/g, " "));   // "too many spaces here"

5. Extract numbers from text

const bill = "Pen 10, Book 120, Bag 800";

const numbers = bill.match(/\d+/g).map(Number);
const total = numbers.reduce((sum, n) => sum + n, 0);

console.log(numbers);   // [10, 120, 800]
console.log(total);     // 930

6. Hide part of a phone number

const phone = "9876543210";

console.log(phone.replace(/\d(?=\d{4})/g, "*"));   // "******3210"

7. camelCase to separate words

function camelToWords(text) {
  return text
    .replace(/([a-z])([A-Z])/g, "$1 $2")
    .toLowerCase();
}

console.log(camelToWords("backgroundColorValue"));   // "background color value"

8. Capitalise every word

function titleCase(text) {
  return text.toLowerCase().replace(/\b\w/g, (c) => c.toUpperCase());
}

console.log(titleCase("jAVAsCRIPT is FUN"));   // "Javascript Is Fun"

9. Highlight a search word

function highlight(text, word) {
  const safe = word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");   // escape special characters
  return text.replace(new RegExp(safe, "gi"), (match) => `<mark>${match}</mark>`);
}

console.log(highlight("JavaScript is fun. I love javascript.", "javascript"));
// "<mark>JavaScript</mark> is fun. I love <mark>javascript</mark>."

Whenever you put user input inside a RegExp, escape it first, as shown above.

10. Parse a log line

const line = "2026-10-02 14:30:05 ERROR Database connection failed";

const { date, time, level, message } = line.match(
  /^(?<date>\S+) (?<time>\S+) (?<level>[A-Z]+) (?<message>.+)$/
).groups;

console.log(level);     // "ERROR"
console.log(message);   // "Database connection failed"

Common mistakes

  • Forgetting ^ and $ in validation. /\d{6}/.test("abc123456xyz") is true. Use /^\d{6}$/.

  • Forgetting the g flag when you want all matches. Without it, match and replace handle only the first match.

  • Using the g flag with test on a regex stored in a variable. The regex remembers its position (lastIndex), so repeated calls can give alternating results.

    const re = /a/g;
    console.log(re.test("a"));   // true
    console.log(re.test("a"));   // false (it continued from the old position)

    For checks, do not use g.

  • Not escaping special characters like ., ?, +, (. /3.14/ also matches 3x14.

  • Using . when you mean a real dot. Write \..

  • Writing \d in a normal string for new RegExp. Write "\\d" (a double backslash).

  • Greedy matching grabbing too much. Use the lazy form .*?.

  • Not handling null from match. "abc".match(/\d/g).length crashes. Use ?? [].

  • Trying to validate everything with one huge regex (emails, URLs, HTML). Use a simple pattern, or a proper library.

  • Parsing HTML with regex. HTML is too complex. Use the DOM instead.

  • Writing patterns that can freeze the page (like /(a+)+$/ on long text). Avoid nested repeating groups on user input.

  • Writing one unreadable monster pattern. Break it into smaller parts, use named groups, and add a comment above it.

Practice

Use the Regex Tester to try each one before you write the code.

  1. Check if a text contains the word "js" (ignore capital letters).
  2. Validate a 4-digit OTP: exactly 4 digits and nothing else.
  3. Validate an Indian mobile number: 10 digits, starting with 6, 7, 8 or 9.
  4. Find all the numbers in "Order 15 has 3 items and costs 450".
  5. Extract all the hashtags from "I love #html, #css and #javascript".
  6. Replace all multiple spaces in a text with a single space.
  7. Turn "2026-10-02" into "02-10-2026" using groups in replace.
  8. Split "apple, banana;cherry orange" into an array of 4 fruits.
  9. Write a isStrongPassword(text) function: at least 8 characters, with one capital letter, one small letter, one digit and one special character.
  10. Write maskEmail("riya@example.com") that returns "r***@example.com".
  11. Challenge: write parseQuery("?name=Riya&age=22") using matchAll and named groups, returning { name: "Riya", age: "22" }.

Recap

  • A regular expression is a pattern written as /pattern/flags or new RegExp("pattern", "flags").
  • Character classes: [abc], [a-z], [^0-9], \d, \w, \s, and . for any character. Escape special characters with \.
  • Quantifiers: *, +, ?, {n}, {n,m}. They are greedy by default, add ? to make them lazy.
  • Anchors: ^, $, \b. Use ^...$ to check the whole text.
  • Groups: ( ) captures, (?<name>...) names a group, (?:...) only groups. | means OR.
  • Flags: g (all matches), i (ignore case), m (multiline), s, u, y, d.
  • Lookahead and lookbehind: (?=...), (?!...), (?<=...), (?<!...) check the surroundings without consuming them.
  • Methods: test, match, matchAll, replace, replaceAll, split, search, exec. match can return null.
  • Do not use the g flag with test in checks, escape user input before putting it in a pattern, and keep patterns simple and well tested.
  • Practise patterns in the Regex Tester.
  • Next you will learn error handling: how to deal with things that go wrong using try, catch, finally and throw.