Regular expressions look like line noise until they click — then they become the fastest way to validate, search, and extract text in any codebase. This guide breaks down flags, capture groups, greedy vs lazy matching, and gives you ready-to-use patterns for validating Indian phone numbers, PAN, GST numbers, and email addresses.
A regular expression (regex) is a sequence of characters that defines a search pattern. Instead of searching for an exact string, regex lets you describe a pattern — "any 10 digits," "an email-like structure," "a word starting with capital A" — and match all text fitting that description.
Flags modify how a regex pattern behaves during matching. They're appended after the closing slash in JavaScript syntax: /pattern/flags.
| Flag | Name | Effect | Example |
|---|---|---|---|
| g | Global | Finds ALL matches, not just the first | /cat/g on "cat cat cat" finds 3 matches |
| i | Case-insensitive | Ignores letter case during matching | /cat/i matches "Cat", "CAT", "cAt" |
| m | Multiline | ^ and $ match start/end of each line, not whole string | Useful for matching line-by-line in multi-line text |
| s | DotAll | . matches newline characters too (normally it doesn't) | Useful for matching across line breaks |
| u | Unicode | Enables full Unicode matching, including emoji and non-Latin scripts | Important when working with Hindi or other Indian language text |
Flags combine freely: /pattern/gi means "find all matches, case-insensitive." This is the most commonly used combination for search-and-replace operations across an entire document.
Capture groups, denoted by parentheses (), let you extract specific parts of a match rather than just confirming a pattern exists.
Pattern: (\d{3})-(\d{4})
Input: "555-1234"
Group 1: "555"
Group 2: "1234"
Pattern: (?<area>\d{3})-(?<number>\d{4})
Input: "555-1234"
Result: { area: "555", number: "1234" }
Named groups, using the (?<name>...) syntax, are especially useful in production code — referencing match.groups.area is far more readable than remembering that group 1 means "area code."
Practical use: Extracting structured data from an Indian invoice number like "INV-2026-00451" using pattern INV-(?<year>\d{4})-(?<seq>\d+) instantly gives you the year and sequence number as separate, named values.
This single concept trips up more developers than almost anything else in regex. Quantifiers (*, +, {n,m}) are greedy by default — they match as much text as possible.
Input: "<a><b>" Greedy: <.*> → matches "<a><b>" (entire string) Lazy: <.*?> → matches "<a>" (stops at first >)
| Quantifier | Type | Behaviour |
|---|---|---|
| * | Greedy | Matches as much as possible (0 or more) |
| *? | Lazy | Matches as little as possible (0 or more) |
| + | Greedy | Matches as much as possible (1 or more) |
| +? | Lazy | Matches as little as possible (1 or more) |
This is the #1 cause of broken HTML/XML regex. Trying to match individual tags with <.*> across multiple tags greedily consumes everything between the first < and the LAST >. Use <.*?> for lazy matching, or better — use a proper HTML parser, not regex, for HTML.
| Format | Regex Pattern | Matches |
|---|---|---|
| Indian mobile (10-digit) | ^[6-9]\d{9}$ | 9876543210 |
| Indian mobile with +91 | ^(\+91[\-\s]?)?[6-9]\d{9}$ | +91 9876543210, 919876543210 |
| PAN card | ^[A-Z]{5}\d{4}[A-Z]{1}$ | ABCDE1234F |
| GST number | ^\d{2}[A-Z]{5}\d{4}[A-Z]{1}\d[Z][A-Z\d]$ | 27AAACR5055K1Z3 |
| PIN code (6-digit) | ^[1-9]\d{5}$ | 400001, 110001 |
| IFSC code | ^[A-Z]{4}0[A-Z0-9]{6}$ | SBIN0001234 |
| Aadhaar (12-digit, basic) | ^\d{4}\s?\d{4}\s?\d{4}$ | 1234 5678 9012 |
Format validation ≠ existence verification. These patterns confirm a string follows the correct structural format — they do NOT verify the PAN, GST number, or Aadhaar is actually issued or active. For legal/financial use cases, always verify against the official government database (Income Tax e-filing portal for PAN, GSTN portal for GST, UIDAI for Aadhaar) in addition to format checks.
^[\w.+-]+@[\w-]+\.[a-zA-Z]{2,}$
This pattern matches the vast majority of real-world email addresses — letters, numbers, dots, plus signs, and hyphens in the local part; a domain with at least one dot; and a TLD of 2+ letters.
Fully RFC 5322 compliant email validation is famously complex — the "official" regex is hundreds of characters long and still has edge cases. For production systems, combine basic regex validation (catches obvious typos) with an actual verification email (confirms the address truly exists and is accessible) — regex alone cannot guarantee deliverability.
| Symbol | Meaning | Example Use |
|---|---|---|
| \d | Any digit (0-9) | Matching numbers |
| \w | Word character (letters, digits, underscore) | Matching identifiers, usernames |
| \s | Whitespace (space, tab, newline) | Matching gaps between words |
| ^ | Start of string (or line, with m flag) | Anchoring pattern to the beginning |
| $ | End of string (or line, with m flag) | Anchoring pattern to the end |
| {n,m} | Between n and m repetitions | \d{10} matches exactly 10 digits |
| [abc] | Character class — matches a, b, or c | [6-9] matches any digit 6 through 9 |
| (?:...) | Non-capturing group | Groups without creating a capture reference |
| (?=...) | Positive lookahead | Match only if followed by a pattern |
| | | OR — alternation | cat|dog matches "cat" or "dog" |
| Mistake | Why It's a Problem | Fix |
|---|---|---|
| Using regex to parse HTML/XML | HTML is not a regular language — nested tags break regex matching unpredictably | Use a proper parser (DOMParser, BeautifulSoup, cheerio) for HTML/XML |
| Forgetting to escape special characters | Characters like . ( ) [ ] have special meaning; unescaped they don't match literally | Escape with backslash: \. \( \) to match the literal character |
| Overcomplicating email validation | Trying to write a "perfect" RFC-5322 regex wastes time and still has edge cases | Use a practical pattern plus actual email verification, not regex alone |
| Not testing against edge cases | A pattern that works on 5 test inputs may fail on unicode, empty strings, or unusual formats | Test with edge cases: empty input, very long input, special characters, unicode |
| Catastrophic backtracking with nested quantifiers | Patterns like (a+)+ can cause exponential time complexity on certain inputs, freezing the application | Avoid nested quantifiers on the same character class; test performance on malicious-looking input |
ToolLoom builds free developer and productivity tools for Indian students, professionals, and creators. Found a bug or want a feature in the Regex Tester? Email us at contact@toolloom.in