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.
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")istrue. Use/^\d{6}$/. -
Forgetting the
gflag when you want all matches. Without it,matchandreplacehandle only the first match. -
Using the
gflag withteston 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 matches3x14. -
Using
.when you mean a real dot. Write\.. -
Writing
\din a normal string fornew RegExp. Write"\\d"(a double backslash). -
Greedy matching grabbing too much. Use the lazy form
.*?. -
Not handling
nullfrommatch."abc".match(/\d/g).lengthcrashes. 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.
- Check if a text contains the word
"js"(ignore capital letters). - Validate a 4-digit OTP: exactly 4 digits and nothing else.
- Validate an Indian mobile number: 10 digits, starting with 6, 7, 8 or 9.
- Find all the numbers in
"Order 15 has 3 items and costs 450". - Extract all the hashtags from
"I love #html, #css and #javascript". - Replace all multiple spaces in a text with a single space.
- Turn
"2026-10-02"into"02-10-2026"using groups inreplace. - Split
"apple, banana;cherry orange"into an array of 4 fruits. - Write a
isStrongPassword(text)function: at least 8 characters, with one capital letter, one small letter, one digit and one special character. - Write
maskEmail("riya@example.com")that returns"r***@example.com". - Challenge: write
parseQuery("?name=Riya&age=22")usingmatchAlland named groups, returning{ name: "Riya", age: "22" }.
Recap
- A regular expression is a pattern written as
/pattern/flagsornew 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.matchcan returnnull. - Do not use the
gflag withtestin 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,finallyandthrow.