Code of the Day
BeginnerPractical matching

Common patterns

A practical catalogue of real-world regex patterns for emails, URLs, dates, phone numbers, and more — with honest caveats about what regex can and cannot do.

Regular ExpressionsBeginner12 min read
Recommended first
By the end of this lesson you will be able to:
  • Write and adapt working patterns for email, phone, date, and number formats
  • Explain why "good enough" patterns are often better than RFC-compliant ones
  • Use regex101.com as a debugging and exploration tool

The most useful regex skill is not memorizing patterns — it is knowing the shape of a problem well enough to compose a pattern quickly and verify it. This lesson catalogues the patterns you will reach for most often, explains the tradeoffs in each, and teaches you to adapt them.

The "good enough" principle

A fully RFC-compliant email regex runs to several hundred characters. Nobody reads it, few people can debug it, and it still rejects some theoretically valid addresses. For almost every real-world use case, a shorter pattern that handles 99 % of inputs correctly is far better.

Write the simplest pattern that correctly handles your actual data. If an edge case arises, add exactly what handles that case. Do not start from the most permissive possible interpretation of a specification.

Email addresses

A pragmatic email pattern:

/^[\w.+-]+@[\w-]+\.([\w-]+\.)*[a-z]{2,}$/i

Breaking it down:

PartMeaning
^[\w.+-]+Local part: word chars, dots, plus, hyphen
@Literal at-sign
[\w-]+Domain name label
\.Literal dot
([\w-]+\.)*Optional subdomains, each ending with a dot
[a-z]{2,}$TLD: at least 2 letters
const emailRe = /^[\w.+-]+@[\w-]+\.([\w-]+\.)*[a-z]{2,}$/i;
emailRe.test("user@example.com");        // true
emailRe.test("user.name+tag@sub.co.uk"); // true
emailRe.test("@example.com");            // false — no local part
emailRe.test("user@");                   // false — no domain

For form validation, HTML's type="email" input already applies a reasonable email check. Use regex for server-side validation or when working with text that isn't in a form field.

Phone numbers

Phone formats vary enormously by country. A pattern that matches common US formats:

/^(\+1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$/

This handles:

  • 555-123-4567
  • (555) 123-4567
  • 555.123.4567
  • +1 555 123 4567
const phoneRe = /^(\+1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$/;
phoneRe.test("555-123-4567");    // true
phoneRe.test("(555) 123-4567"); // true
phoneRe.test("5551234567");     // true
phoneRe.test("55-1234");        // false

For international numbers, consider a library like libphonenumber rather than regex — the format space is too large for a simple pattern to handle reliably.

Dates

An ISO date (YYYY-MM-DD):

/^\d{4}-\d{2}-\d{2}$/

Note that this matches "9999-99-99" — regex cannot validate whether the date is real (February 30 would pass). For business logic, parse to a Date object after the structural match passes.

Common US date MM/DD/YYYY:

/^\d{1,2}\/\d{1,2}\/\d{4}$/

A flexible extractor that finds dates in text:

const text = "Shipped 2024-03-15, expected 2024-03-22";
const dates = text.match(/\d{4}-\d{2}-\d{2}/g);
// ["2024-03-15", "2024-03-22"]

Integers and decimal numbers

// Match an integer (optional leading minus)
/-?\d+/

// Match a decimal (requires at least one digit on each side of the dot)
/-?\d+\.\d+/

// Match either integer or decimal
/-?\d+(\.\d+)?/

// Match numbers in scientific notation
/-?\d+(\.\d+)?([eE][+-]?\d+)?/
"The value is -3.14".match(/-?\d+(\.\d+)?/);  // ["-3.14", ".14"]
"Gain: +2.5%".match(/[+-]?\d+(\.\d+)?/);      // ["+2.5", ".5"]

The .14 in the first result is the captured group, not a separate match — the full match is "-3.14". JavaScript's .match() without g returns the full match at index 0 and captured groups at subsequent indices.

IP addresses

A simplified IPv4 pattern:

/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/

This matches the structural form but allows 999.999.999.999. To validate ranges (0–255) requires either a more complex pattern or parsing each octet after the structural match:

function isIPv4(s) {
  if (!/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(s)) return false;
  return s.split(".").every(n => Number(n) <= 255);
}
isIPv4("192.168.1.1");   // true
isIPv4("192.168.1.999"); // false
isIPv4("192.168.1");     // false

This pattern — regex for structure, code for value constraints — is the right architecture for "mostly regular" data.

URLs

A practical URL extractor (not a validator):

/https?:\/\/[\w-]+(\.[\w-]+)+([\/\w\-.?=&%#]*)?/g
const text = "Visit https://example.com/path?q=1 or http://other.org";
const urls = text.match(/https?:\/\/[\w-]+(\.[\w-]+)+([\/\w\-.?=&%#]*)?/g);
// ["https://example.com/path?q=1", "http://other.org"]

For strict URL validation, the browser's URL constructor is more reliable than regex:

function isValidUrl(s) {
  try { new URL(s); return true; }
  catch { return false; }
}

Whitespace and empty lines

Strip leading and trailing whitespace:

"  hello world  ".replace(/^\s+|\s+$/g, "");  // "hello world"
// Or just use .trim() — built-in and clearer

Find empty or whitespace-only lines:

const emptyLine = /^\s*$/m;

Collapse multiple spaces into one:

"too   many   spaces".replace(/\s{2,}/g, " "); // "too many spaces"

Hex colours

// CSS hex colour (#rgb or #rrggbb)
/^#([a-f0-9]{3}|[a-f0-9]{6})$/i
/^#([a-f0-9]{3}|[a-f0-9]{6})$/i.test("#ff0000"); // true
/^#([a-f0-9]{3}|[a-f0-9]{6})$/i.test("#f00");    // true
/^#([a-f0-9]{3}|[a-f0-9]{6})$/i.test("#gg0000"); // false
JavaScript — editable, runs in your browser

The tool you should have open

regex101.com is the single best companion tool for writing regex. It:

  • Highlights each match in real time as you type
  • Explains every part of your pattern in a sidebar
  • Shows the engine's step-by-step evaluation (invaluable for debugging)
  • Supports JavaScript, Python, PHP, Go, and other flavours

Whenever a pattern is not matching what you expect, paste it into regex101 and look at the explanation — the engine's reasoning is usually immediately obvious.

Where to go next

You are ready for the Practical matching lab — apply these patterns and all the beginner syntax to a realistic block of text. After that, the intermediate tier opens up: capturing groups, named groups, backreferences, and lookarounds.

Finished reading? Mark it complete to track your progress.

On this page