Regular Expressions (Regex) for Beginners
A regular expression (regex) is a compact pattern that describes a set of strings. One expression can find every email address in a log, validate a phone number field, extract the order id from a URL or rename a thousand files at once. Every major language — JavaScript, Python, Java, Go, C#, PHP — and every editor and command-line tool understands the same core syntax.
This guide teaches that core: literal characters, character classes, quantifiers, anchors, groups and flags — with a cheat sheet, patterns you can copy, and the mistakes that make a regex match too much or too little.
Literal characters and metacharacters
Most characters match themselves: the pattern cat matches "cat" inside "concatenate". Fourteen characters have a special meaning (ECMAScript calls them syntax characters) and are escaped with a backslash to be matched literally: . ^ $ * + ? ( ) [ ] { } | and \ itself. The forward slash / is not one of them; it only needs escaping inside a JavaScript regex literal such as /a\/b/, where it would otherwise end the pattern. The dot is the one that bites — a.b matches "a" followed by any character then "b", so "a.b" also matches "aXb".
colou?r matches "color" and "colour"
\d{3}-\d{4} matches 555-1234
\. matches a literal dot
https?:// matches http:// and https://Character classes
- [abc] — one of a, b or c. [a-z] — any lower-case letter. [^0-9] — anything except a digit.
- \d — a digit [0-9]. \w — a word character [A-Za-z0-9_]. \s — whitespace (space, tab, newline). Upper case negates: \D, \W, \S.
- . — any character except a newline (unless the s flag is set).
- Unicode — in JavaScript, \w and \d stay ASCII-only even with the u or v flag. Use the u flag with property escapes instead: \p{L} for any letter, \p{Script=Arabic} for Arabic letters, \p{Script=Devanagari} for Hindi. Hindi vowel signs and Arabic diacritics are combining marks (\p{M}), not letters, so match a whole word with [\p{L}\p{M}]+ rather than \p{L}+.
Quantifiers: how many times
- * — zero or more. + — one or more. ? — zero or one.
- {3} — exactly three. {2,5} — two to five. {2,} — two or more.
- Quantifiers are greedy: .* takes as much as possible. Add ? to make them lazy: .*? stops at the first chance. <.+> on "<a><b>" matches the whole string; <.+?> matches "<a>".
<.+> runs on to the last > it can reach; adding ? makes it lazy, so it stops at the first.<.+>Greedy
Takes as much as possible: the whole string
[match: <a><b> — end of match]
<.+?>Lazy
Stops at the first chance: <a> only
[match: <a> — end of match]<b>
Highlighted: the part of each input the pattern matches, as JavaScript finds it.
Anchors, groups and alternation
- ^ — start of the string (or line with the m flag). $ — end. \b — a word boundary, so \bcat\b matches "cat" but not "concatenate".
- ( ) — a capturing group: (\d{4})-(\d{2}) captures the year and month separately. (?: ) groups without capturing. (?<year>\d{4}) names the group.
- | — alternation: jpg|png|gif matches any of the three. Wrap it in a group to limit its scope: \.(jpg|png|gif)$.
- (?=…) and (?!…) — lookahead: match only if what follows does (or does not) match, without consuming it. Used for password rules: ^(?=.*\d)(?=.*[A-Z]).{12,}$.
Flags
- i — case-insensitive. g — global (find all matches, not just the first). m — multiline: ^ and $ match at line boundaries.
- s — dotall: . also matches newlines. u — Unicode mode. x (some engines) — ignore whitespace and allow comments in the pattern.
Patterns you can copy
Email (practical): ^[^\s@]+@[^\s@]+\.[^\s@]{2,}$
Saudi mobile: ^(?:\+966|0)5\d{8}$
Indian mobile: ^(?:\+91|0)?[6-9]\d{9}$
ISO date: ^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$
UUID: ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
URL (loose): ^https?://[^\s/$.?#].[^\s]*$
Hex color: ^#(?:[0-9a-fA-F]{3}){1,2}$
Strong password: ^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^\w\s]).{12,}$
Trim whitespace: ^\s+|\s+$
Extract order id: /orders/(\d+)Mistakes that match too much or too little
- Forgetting anchors: \d{4} "validates" 12345 because it finds 1234 inside it. Use ^\d{4}$.
- Unescaped dots: example.com matches "exampleXcom". Write example\.com.
- Greedy quantifiers across the whole line: "(.*)" on a line with two quoted strings captures both.
- Catastrophic backtracking: nested quantifiers like (a+)+ on a non-matching input can take seconds or hang. Keep quantifiers simple and test with long inputs.
- Validating email with a giant pattern: the practical pattern above plus a confirmation email beats a 400-character regex that still rejects valid addresses.
- Locale assumptions: in JavaScript \w never matches Arabic or Devanagari letters, even in Unicode mode. Use [\p{L}\p{M}] with the u flag.
\d{4}Missing anchors
Finds 1234 inside 12345, so 12345 “validates”
[match: 1234 — end of match]5
^\d{4}$Fix
^ and $ tie it to the whole input: no match
12345
example.comUnescaped dot
The dot matches any character, here the X
[match: exampleXcom — end of match]
example\.comFix
\. matches only a literal dot: no match
exampleXcom
Highlighted: the part of each input the pattern matches, as JavaScript finds it.
Frequently asked questions
What is a regular expression used for?
Finding, validating, extracting and replacing text that follows a pattern: form validation, log searching, parsing ids out of strings, bulk renaming and editor search-and-replace.
What does \d mean in regex?
Any single digit, 0–9. \d{3} means exactly three digits; \d+ means one or more.
What is the difference between * and + in regex?
* matches zero or more repetitions (so the element may be absent); + requires at least one.
Is regex the same in every language?
The core syntax is shared, but details differ: lookbehind support, named groups, Unicode classes and flags vary between JavaScript, Python (re), Java, PCRE (PHP) and Go (RE2, which has no backtracking).
How do I test a regex?
Paste the pattern and sample text into a tester that highlights matches and groups, then try inputs that should not match. Include long and unusual inputs to catch performance problems.
Can I use regex to parse HTML or JSON?
For a quick one-off extraction, yes. For anything reliable, no — nested structures need a real parser. Use a JSON tree viewer or JSONPath for JSON.
References
- ECMAScript® Language Specification (ECMA-262)The JavaScript language standard, including the RegExp grammar and semantics, JSON.parse/JSON.stringify and the Date object.
- Regular expressions — JavaScript guide — MDNA practical reference for regular-expression syntax as implemented in JavaScript engines: character classes, groups, quantifiers, flags.
Practice exercise
Write and review three regexes for a parcel tracking page
You test the "Track a parcel" feature of a courier app (fictional): a web form that takes a tracking number, a status page, and the API behind them. The material below gives the two requirements, the validation pattern the developer wrote for the form, three status lines seen in the test environment and a short extract of the server log. You need regular expressions in three places: to review the form validation, to extract values from the status line inside an automated check, and to search the log.
Work on paper first, then check each pattern in the Regex Tester: the form pattern against each of your step 2 inputs, and the status-line and log patterns against the material pasted as the sample text. Try at least one input per pattern that must not match. Use JavaScript syntax as in the guide. For step 2, the Validation Test Generator can turn the field line trackingNumber: string, required, length=13, pattern= followed by your corrected pattern into a checklist of validation test cases (empty input, 12 and 14 characters, a value that breaks the pattern). Keep the type string: without it, a field name ending in "Number" is read as a whole number. The checklist shows placeholders, so you still choose the concrete inputs. Allow 20–30 minutes.
Courier app (fictional) - release 3.2 adds "Track a parcel": form, status page, API
Requirements
FR-21 A tracking number has 13 characters: 2 letters, 9 digits, 2 letters, for
example RB123456789SA. Letters may be typed in lower case; the form converts
them to upper case before the search.
FR-22 The status page shows one status line in this format:
Parcel <tracking number>: <status> <YYYY-MM-DD> <HH:MM> (<city>)
where <status> is one of: accepted, in transit, out for delivery, delivered.
Validation currently in the form (JavaScript, no flags)
[A-Z]{2}\d{9}[A-Z]{2}
Status lines seen in the test environment
Parcel RB123456789SA: delivered 2026-10-02 14:05 (Riyadh)
Parcel CP987654321IN: out for delivery 2026-10-02 07:45 (New Delhi)
Parcel LX456789123SA: in transit 2026-10-01 09:30 (Jeddah)
Server log extract (one request per line: time, method, path, status, duration)
2026-10-02T14:05:12Z GET /api/v2/parcels/RB123456789SA/events 200 83ms
2026-10-02T14:05:13Z GET /api/v2/parcels/RB123456789SA 503 1204ms
2026-10-02T14:05:14Z POST /api/v2/parcels 201 44ms
2026-10-02T14:05:15Z GET /api/v2/parcels/CP512345678IN 200 512ms
2026-10-02T14:05:16Z GET /api/v2/rates?from=RUH&to=DEL 500 31ms
2026-10-02T14:05:17Z GET /api/v2/parcels/EE500000000SA 404 12ms
2026-10-02T14:05:18Z POST /api/v2/parcels 502 2003ms- Compare the developer’s pattern with FR-21 using the guide’s list of mistakes: which inputs does it accept that it should reject, and which does it reject that it should accept? Write the corrected pattern with its flags.
- List the test inputs for the corrected pattern: at least three that must match and six that must not, each chosen to break one part of the rule (length, letters, digits, extra characters).
- For the automated check of the status page, write one pattern with named groups that extracts the tracking number, the status, the date, the time and the city from a status line (FR-22). It must match all three example lines and must not match a line whose status is misspelled, for example "delivred".
- Write a pattern that finds, in the log extract, only the lines where a request to a
/api/v2/parcelspath got a 5xx status. State which flags it needs, which lines it matches, and why5\d\don its own is not enough. - Note any question the requirements leave open, and whom you would ask.
Show the model answer
Step 1. The developer’s pattern [A-Z]{2}\d{9}[A-Z]{2} has two defects, one in each direction of the guide’s list of mistakes. It matches too much because it has no anchors: it accepts any input that contains a valid number somewhere, so XRB123456789SAX, RB123456789SA123 and RB123456789SA with a trailing space all pass. It matches too little because [A-Z] without the i flag rejects rb123456789sa, which FR-21 says the form must accept. Corrected pattern: ^[A-Z]{2}\d{9}[A-Z]{2}$ with the i flag, written /^[A-Z]{2}\d{9}[A-Z]{2}$/i in JavaScript; ^[A-Za-z]{2}\d{9}[A-Za-z]{2}$ without flags is equivalent. One caution before you report the second defect: if the form converts the input to upper case before this check runs, lower-case letters already pass. That is a question for the developer, and the test input rb123456789sa settles it either way.
Step 2. Must match: RB123456789SA, rb123456789sa (case) and CP987654321IN (another prefix and country). Must not match: RB12345678SA (8 digits) and RB1234567890SA (10 digits), the two boundary lengths; RB123456789S (a single trailing letter); R1123456789SA (a digit where a letter must be); XRB123456789SAX and RB123456789SA123 (extra characters, which only the anchors reject); RB123456789SA with a trailing space (common after copy and paste); RB-123456789-SA (separators); and an empty input. Each invalid input breaks exactly one part of the rule, so when one of them is accepted you know which part of the pattern is wrong. The expected result for every input is "accepted" or "rejected", as in any validation test case.
Step 3. One pattern that satisfies FR-22: ^Parcel (?<id>[A-Z]{2}\d{9}[A-Z]{2}): (?<status>accepted|in transit|out for delivery|delivered) (?<date>\d{4}-\d{2}-\d{2}) (?<time>\d{2}:\d{2}) \((?<city>[^()]+)\)$. The points that matter: the alternation for the status sits inside its own group, so its scope is only the status; written without the group, accepted|in transit|… would split the whole pattern into four alternatives and match almost any text that contains "in transit". The parentheses around the city are metacharacters, so the literal ones are escaped as \( and \). The city is [^()]+ rather than \w+: \w+ matches "Riyadh" but not "New Delhi" because of the space, and, as the guide warns, never an Arabic or Devanagari city name if the page is localised; a negated class, or [\p{L}\p{M} ]+ with the u flag, handles both. The anchors ^ and $ make the check reject a line with anything missing or added. On the first example line the groups are id = RB123456789SA, status = delivered, date = 2026-10-02, time = 14:05 and city = Riyadh; on the second, status = out for delivery and city = New Delhi. A line with "delivred" does not match at all, so the check fails instead of reporting a pass, which is exactly what a test needs: a loose pattern that accepts a wrong value makes the test pass when it should fail (a false-negative result). In the test, compare each group with the expected value, for example m.groups.status === "delivered". The date group \d{4}-\d{2}-\d{2} is enough here because the test compares the value itself; use the stricter ISO date pattern from the guide only when the purpose of the test is the format. In the automated check the pattern runs on one status line and needs no flag; to try it in the Regex Tester against the whole material, turn on the m flag, for the reason explained in step 4.
Step 4. A pattern that finds only the two 5xx responses on parcel paths: ^\S+ \S+ /api/v2/parcels(?:/\S*)? 5\d\d \d+ms$ with the m flag, so that ^ and $ match at line boundaries, and the g flag in the tester to see every match; in a JavaScript regex literal the slashes are written \/. Read from left to right: the timestamp, the method, the path /api/v2/parcels optionally followed by / and more non-space characters (/RB123456789SA or /RB123456789SA/events), a status that starts with 5, and the duration. It matches exactly two lines: the GET with 503 and the POST with 502. 5\d\d on its own finds nine matches on six of the seven log lines, because it finds digits anywhere: 567 inside RB123456789SA on two lines, 512 and then 567 inside CP512345678IN, 512 again in the duration 512ms, 500 inside EE500000000SA, and the 500 on /api/v2/rates, which is not a parcel path; only the 503 and the 502 are the responses you want. A space on each side of the status, \s5\d\d\s, removes the false matches in tracking numbers and durations but keeps the rates line; the path condition removes that one too. Without the m flag the pattern matches nothing, because ^ and $ then mean the start and the end of the whole text. It is the same rule as in the form: without context and anchors a regex matches too much.
Step 5. Open questions for the product owner or the developer: does the form validate before or after converting to upper case, and is the input trimmed (a trailing space from copy and paste is common)? Should digits typed on an Arabic keyboard as Arabic-Indic digits be accepted or converted? \d in JavaScript matches only 0–9, so RB١٢٣٤٥٦٧٨٩SA is rejected. Is the status list in FR-22 complete, and what does the line look like when a parcel has no city yet? And which error message does the form show for each rejected input, so that the test can assert it?
If your patterns differ, check them in the Regex Tester. The form pattern, tried on each step 2 input (or on all of them, one per line, with the m flag), must accept the three valid inputs and reject all the others. Against the material with the m flag, the status-line pattern must match the three status lines with the right group values, and reject the misspelled line when you add it, and the log pattern must highlight exactly two log lines. A different but equivalent pattern is fine — \d{2} and [0-9]{2} are the same — as long as it is anchored where it validates, escapes every literal metacharacter and keeps the alternation inside a group. None of these patterns puts a repeating quantifier on a group that contains one, like (a+)+ in the guide: the only quantified group, (?:/\S*)?, is optional and runs at most once, so the risk of catastrophic backtracking is low; paste a long input to be sure. Then put the status-line pattern into the Regex Explainer and read it token by token: it is a quick way to find a mistake you cannot see.
Your progress on this topic
Not started
Mark the topic complete when you have studied it and done the exercise.
Sign in to track your progress
Mark topics complete, continue where you left off and unlock specialisation exams.
Part of the QA Roadmap
Before this topic
Next topics
Tools mentioned in this guide
Test regular expressions live with match highlighting, capture groups and replace preview.
Get a plain-English, token-by-token explanation of any regular expression.
Build regular expressions from a library of tested patterns (email, URL, IP, dates, IBAN, Saudi/Jordan phone numbers…) and custom rules.
Compare two texts line by line or word by word and highlight differences.
Evaluate JSONPath expressions ($.store.book[*].author) against JSON and see matches live.