04/11/2026 10:00am

What Is Regex? Learn Regular Expressions One Piece at a Time
#what is regex
#regular expression
#regex pattern
#regex python
#regex javascript
#regex phone number
#regex email validation
Picture this: your team lead forwards a messy block of customer chat and asks you to "pull out every mobile number and email so we can build a contact list." Some numbers have dashes, some don't, and an office landline is mixed in too. You could read it line by line. Once the text runs to a thousand lines, though, that job eats your whole day and you will still miss some.
This is exactly the kind of job Regex, short for Regular Expression, was made for. Today Superdev Academy will build one pattern for this single task, piece by piece, and run every step for real in both JavaScript and Python so you can see the actual output. You don't need to memorise the whole symbol table first. You add each piece only when the task needs it.
What is Regex, in plain words?
The MDN guide to regular expressions describes them as patterns used to match character combinations in strings. Put simply, you describe what the thing you want looks like, instead of typing the thing itself. Rather than searching for 0812345678, you say "find a 0 followed by nine more digits" and let the program find every number with that shape.
Here is the sample text we'll use throughout the article. It is a Thai chat line, so the non-English words are just noise around the data we want:
const text = "ติดต่อ: 081-234-5678, 0898765432 อีเมล sales@example.com / a.team@shop.co.th โทร 02-123-4567";It contains two mobile numbers (one with dashes, one without), two email addresses, and one office number we don't want. The goal is to pick out exactly what we need.
How to use regex, piece 1: literal characters and \d
A plain character in a pattern means itself, so the pattern 0 finds a zero. Symbols that start with a backslash are shorthand for a whole group of characters. The three you'll use most:
\d: a digit 0-9\w: a Latin letter, digit or underscore (_)\s: whitespace, such as a space, tab or line break
MDN states that in JavaScript \d is equivalent to [0-9] and \w to [A-Za-z0-9_]. Let's try \d on the sample text. The g after the pattern is a Flag that means "find every match, don't stop at the first".
console.log(text.match(/\d/g).length);
// 29We get 29 digits, but one at a time they're useless. We need to tell Regex how many digits should appear in a row.
Piece 2: saying "how many" with quantifiers
A quantifier goes right after the piece before it and says how many times that piece can repeat.
+: one or more*: zero or more (none at all is fine)?: optional (zero or one time){n}: exactly n, and{n,m}: between n and m
console.log(text.match(/\d+/g));
// [ '081', '234', '5678', '0898765432', '02', '123', '4567' ]
console.log(text.match(/0\d{9}/g));
// [ '0898765432' ]\d+ groups digits into runs, but the dashed number gets chopped into three pieces. 0\d{9} (a 0 followed by nine digits) catches the undashed number correctly, but misses 081-234-5678 because of the dashes.
This is where ? helps. We say "a dash may or may not be here" with -?, and split the number into the three blocks Thai numbers are usually written in:
console.log(text.match(/0\d{2}-?\d{3}-?\d{4}/g));
// [ '081-234-5678', '0898765432' ]Both mobile numbers come out, and the office number 02-123-4567 stays behind because it only has nine digits and doesn't fit the 3-3-4 shape we wrote.
Piece 3: [] for "any one of these", and a dot that isn't a dot
Square brackets [] form a Character Class, meaning "any single one of these characters". [689] means one character that is 6, 8 or 9. Say your team's rule is to accept only numbers whose second digit is 6, 8 or 9. You would write:
console.log(/^0[689]\d{8}$/.test("0912345678")); // true
console.log(/^0[689]\d{8}$/.test("0212345678")); // falseWhich prefixes are valid is decided by carriers and the numbering authority, and it can change. For a real system, use the rule your team has agreed on, not the example in an article.
The other piece beginners trip over is the dot. In Regex, . does not mean a full stop. It means "any single character" (except a line break). To match an actual dot, escape it as \.
console.log("a.b".match(/./g)); // [ 'a', '.', 'b' ]
console.log("a.b".match(/\./g)); // [ '.' ]Piece 4: ^ and $ when validating input
So far we've been searching inside long text. The other everyday job is validating: checking that what a user typed into a form has the right shape, for example a phone field that must be exactly 10 digits. The search pattern alone gets this wrong:
console.log(/0\d{9}/.test("08123456789"));
// true (even though 11 digits were typed)Regex found ten matching characters somewhere inside, so it reports a pass. The fix is anchors: ^ means the start of the text and $ means the end.
console.log(/^0\d{9}$/.test("0812345678")); // true
console.log(/^0\d{9}$/.test("081234567")); // false (9 digits)
console.log(/^0\d{9}$/.test("08123456789")); // false (11 digits)In Python, use re.fullmatch. The Python re module documentation says it returns a match only if the whole string matches the pattern, which gives the same result as adding ^ and $.
import re
print(re.fullmatch(r"0\d{9}", "0812345678"))
# <re.Match object; span=(0, 10), match='0812345678'>
print(re.fullmatch(r"0\d{9}", "08123456789"))
# NoneNotice the r before the quotes. That is a Python raw string, which stops backslashes from being interpreted before they reach Regex. The Python docs recommend writing patterns this way as the norm. Forget it, and any pattern with a backslash starts behaving strangely.
Keep in mind that JavaScript validation in the browser is only a first gate to help the user. Anything that reaches your system must be checked again on the server. We cover this in more depth in Essential Secure Coding Techniques for Beginners.
Piece 5: () to split a number into parts
Sometimes you want more than "found or not found". You might want the prefix and the rest separately so you can reformat them. Parentheses () create a Group around the part you want to pull out.
const m = "081-234-5678".match(/^(0\d{2})-?(\d{3})-?(\d{4})$/);
console.log(m[1], m[2], m[3]);
// 081 234 5678In Python, you can run re.findall over the whole text. The docs state that when a pattern has more than one group, the result is a list of tuples, so each tuple is one number already split into parts.
text = "ติดต่อ: 081-234-5678, 0898765432 อีเมล sales@example.com / a.team@shop.co.th โทร 02-123-4567"
print(re.findall(r"(0\d{2})-?(\d{3})-?(\d{4})", text))
# [('081', '234', '5678'), ('089', '876', '5432')]Groups at work: normalise the format, then mask the middle
Once you can split the parts, the usual next step is making every number look the same before it goes into a spreadsheet. You don't need a loop for that. Use each language's replace function and refer to each group by its number. In JavaScript that is $1 $2 $3:
console.log(text.replace(/(0\d{2})-?(\d{3})-?(\d{4})/g, "$1-$2-$3"));
// ติดต่อ: 081-234-5678, 089-876-5432 อีเมล sales@example.com / a.team@shop.co.th โทร 02-123-4567In Python, use re.sub and refer to groups as \1 \2 \3. The output is identical, character for character.
print(re.sub(r"(0\d{2})-?(\d{3})-?(\d{4})", r"\1-\2-\3", text))
# ติดต่อ: 081-234-5678, 089-876-5432 อีเมล sales@example.com / a.team@shop.co.th โทร 02-123-4567A more useful trick than it looks: masking the middle digits before you share text with anyone. When you paste a log into the team chat to ask about a bug, customers' full numbers shouldn't leak with it. Swapping the middle block for XXX is enough.
console.log(text.replace(/(0\d{2})-?(\d{3})-?(\d{4})/g, "$1-XXX-$3"));
// ติดต่อ: 081-XXX-5678, 089-XXX-5432 อีเมล sales@example.com / a.team@shop.co.th โทร 02-123-4567The office number 02-123-4567 is untouched, because our pattern never matched it in the first place. That is the payoff of building a pattern piece by piece and testing each step: you know exactly what it will catch, and what it won't.
Pulling out emails, and why Regex email checks are only approximate
With the pieces we already have, a simple email pattern is: one or more letters, digits or dots, then @, then another run of the same.
print(re.findall(r"[\w.]+@[\w.]+", text))
# ['sales@example.com', 'a.team@shop.co.th']That works on our sample. Now try a sentence that ends with a full stop:
print(re.findall(r"[\w.]+@[\w.]+", "Send it to info@example.com."))
# ['info@example.com.']The sentence's full stop comes along, because the pattern allows a dot anywhere. You can fix that case, and another one will turn up. Even the HTML standard says openly that its definition of a valid email address for input type="email" is a willful violation of RFC 5322, which it calls too strict, too vague and too lax to be of practical use. It then defines its own Regex of more than 130 characters instead.
The lesson: Regex can tell you an email looks like an email. It cannot tell you the mailbox exists. Real systems check the rough shape to catch obvious typos, then send a confirmation link to that address.
Greedy vs lazy: why it grabs too much
By default * and + are Greedy: they match as much as possible while the pattern still fits. Try pulling text out of <b> tags:
const html = "<b>ราคา</b> <b>โปร</b>";
console.log(html.match(/<b>.*<\/b>/)[0]);
// <b>ราคา</b> <b>โปร</b>
console.log(html.match(/<b>.*?<\/b>/)[0]);
// <b>ราคา</b>Adding ? after * makes it Lazy (match as little as possible), so it stops at the first </b>. The Python docs explain non-greedy matching with the same kind of example.
Thai digits ๐-๙: where JavaScript and Python disagree
This one matters if your users write in Thai, and we only found it by running the examples ourselves. Suppose someone types their number in Thai digits: ๐๘๑๒๓๔๕๖๗๘.
// JavaScript
console.log(/^\d{10}$/.test("๐๘๑๒๓๔๕๖๗๘")); // false# Python
print(re.fullmatch(r"\d{10}", "๐๘๑๒๓๔๕๖๗๘"))
# <re.Match object; span=(0, 10), match='๐๘๑๒๓๔๕๖๗๘'>
print(re.fullmatch(r"\d{10}", "๐๘๑๒๓๔๕๖๗๘", flags=re.ASCII))
# NoneSame pattern, opposite results.
The Python docs state that for str patterns, \d matches any Unicode decimal digit, not only 0-9. To limit it to 0-9 you need the re.ASCII flag. In JavaScript, \d is 0-9 only. If your system has a JavaScript front end and a Python back end checking the same data, the two sides will disagree immediately. The safe move is to write [0-9] explicitly.
Jobs Regex shouldn't take, and a tool for testing
The <b> example above only illustrates greedy matching. Don't use it to scrape real web pages. HTML nests tags many levels deep, some end tags can be left out, and attributes can appear in any order. Regex can't keep up with all of it. Use the language's HTML parser instead, such as DOMParser in JavaScript or html.parser in Python. The same goes for other structured data: if it's the kind of data described in What Is JSON? A Beginner's Guide, use JSON.parse or json.loads rather than a hand-written pattern.
While you're learning, a tool like regex101 helps a lot: paste a pattern and some sample text, and you can see what each piece matches. Once the pattern works, always run it again in the language you'll actually use, because, as the Thai digits showed, languages don't read every symbol the same way.
If you're still deciding which language to practise in, JavaScript vs Python: Which Language Should Beginners Start With? can help. And to see Regex in Go side by side with JavaScript, read JS2GO EP.25 Using Regular Expressions in JavaScript and Go.
Summary: Regex is describing what the thing looks like
One task, pulling numbers and emails out of a chat, needed only five pieces: shorthand classes like \d, quantifiers like {9} and ?, choices inside [], the anchors ^ and $, and groups (). Those five cover most of the searching and validation work a beginner will meet. Look up the rest in the docs when a task actually needs it.
Remember its limits too. Regex can confirm something looks right, but it can't confirm it's real, and it isn't the tool for taking apart HTML or JSON. Pick a real piece of text you have today, a group chat or a log file, and write a pattern that pulls out what you need, one piece at a time as we did here. Doing it once yourself sticks better than reading a symbol table ten times.
Follow more articles and tips for people starting out as programmers on our channels.
Facebook: Superdev Academy Thailand (https://www.facebook.com/superdev.academy.th)
YouTube: Superdev Academy Channel (https://www.youtube.com/@SuperdevAcademy)
Instagram: @superdevacademy (https://www.instagram.com/superdevacademy/)
TikTok: @superdevacademy (https://www.tiktok.com/@superdevacademy?lang=th-TH)
FAQ: Frequently Asked Questions about This Article
A collection of questions and answers to help you better understand the content of this article.