HomeProjectsBlogContact

Regex Basics for Developers: A Practical, Readable Guide

Regular expressions look like line noise until the building blocks click. Character classes, quantifiers, anchors, groups and lookarounds, with real examples and the pitfalls that cause bugs.

Regex Basics for Developers: A Practical, Readable Guide

Regular expressions have a reputation: powerful, but unreadable. In practice, you only need a handful of building blocks to handle most everyday tasks, like validating formats, extracting values, searching logs and cleaning up text. Once those blocks click, regex stops being scary.

Examples use JavaScript syntax, but the concepts apply in almost every language.

Literal characters

The simplest regex matches text exactly:

/error/.test('Fatal error occurred')   // true

Add flags after the closing slash: i for case-insensitive, g for all matches, m for multiline anchors.

Character classes

Pattern Matches
. Any character except newline
\d A digit (0–9)
\w A word character (letters, digits, underscore)
\s Whitespace (space, tab, newline)
[abc] Any one of a, b or c
[a-z] Any lowercase letter
[^0-9] Anything that's not a digit

Uppercase versions (\D, \W, \S) mean "not" that class.

Quantifiers

Pattern Meaning
* Zero or more
+ One or more
? Zero or one (optional)
{3} Exactly 3
{2,5} Between 2 and 5
{2,} 2 or more
/\d{3}-\d{4}/.test('555-1234')   // true
/colou?r/.test('color')          // true, the "u" is optional

Anchors

  • ^ matches the start of the string (or line with the m flag).
  • $ matches the end.
  • \b matches a word boundary.

Anchors are essential for validation. Without them, a pattern can match part of the input:

/\d{5}/.test('abc12345xyz')      // true: found 5 digits somewhere
/^\d{5}$/.test('abc12345xyz')    // false: the whole string must be 5 digits

Forgetting anchors in validation is one of the most common regex bugs.

Groups and capturing

Parentheses group parts of a pattern and capture what they match:

const match = '2026-03-14'.match(/^(\d{4})-(\d{2})-(\d{2})$/)
// match[1] = '2026', match[2] = '03', match[3] = '14'

Named groups are more readable:

const { year, month } = '2026-03-14'.match(/(?<year>\d{4})-(?<month>\d{2})/).groups

Use (?:...) for a group you don't need to capture, and | for alternatives: /^(jpg|png|webp)$/.

Greedy vs lazy

Quantifiers are greedy by default: they match as much as possible.

'<b>bold</b> and <b>more</b>'.match(/<b>.*<\/b>/)[0]
// '<b>bold</b> and <b>more</b>'   (everything between the first and LAST tag)

'<b>bold</b> and <b>more</b>'.match(/<b>.*?<\/b>/)[0]
// '<b>bold</b>'                   (lazy: as little as possible)

Adding ? after a quantifier makes it lazy.

Lookaheads and lookbehinds

Check what comes before or after without including it in the match:

  • (?=...) followed by
  • (?!...) not followed by
  • (?<=...) preceded by
  • (?<!...) not preceded by
// Price numbers that come after a dollar sign
'Total: $45, tax: $5'.match(/(?<=\$)\d+/g)   // ['45', '5']

Practical examples

// Slug: lowercase letters, digits and single hyphens
/^[a-z0-9]+(?:-[a-z0-9]+)*$/

// Extract all hashtags
text.match(/#\w+/g)

// Collapse repeated whitespace
text.replace(/\s+/g, ' ').trim()

// Find TODO comments in code
/\/\/\s*TODO:?\s*(.+)$/gm

Pitfalls to avoid

  • Don't validate email addresses with a giant regex. Real email rules are extraordinarily complex. Use a simple sanity check (something@something.something), then confirm the address by sending a verification email. (Validation done right.)
  • Don't parse HTML with regex. HTML nesting breaks regex quickly. Use a real parser.
  • Watch out for catastrophic backtracking. Patterns with nested quantifiers, like (a+)+$, can take exponential time on certain inputs. With user-supplied input, that's a denial-of-service risk (ReDoS). Keep patterns simple, avoid nested quantifiers, and limit input length. (Security checklist.)
  • Escape special characters (. * + ? ^ $ ( ) [ ] { } | \ /) when you mean them literally.
  • Anchor validation patterns with ^ and $.

Make regex readable

  • Test patterns interactively in a tool like regex101, which explains each token and shows matches live. (More free tools.)
  • Give complex patterns a named constant and a comment explaining what they match.
  • Write a few unit tests with matching and non-matching examples. Regex is a perfect fit for small, fast tests. (TDD in practice.)
  • If a pattern gets huge, consider plain code instead. Readability matters more than cleverness.

FAQ

What is a regular expression?

A pattern that describes text, used to search, validate, extract or replace parts of strings. Most programming languages and editors support them.

What is the difference between greedy and lazy matching?

Greedy quantifiers match as much text as possible; lazy quantifiers (with a trailing ?, like .*?) match as little as possible.

Should I use regex to validate emails?

Only for a basic sanity check. Email address rules are complex, so confirm addresses by sending a verification email rather than relying on a strict regex.

Keep reading

More from the workbench

All articles →