DEV Community

Hardi
Hardi

Posted on

Regex Tester: Mastering Regular Expressions for Modern Development

Regular expressions are the Swiss Army knife of text processing—powerful, versatile, and notoriously hard to get right on the first try. Whether you're validating user input, parsing logs, or transforming data, regex is indispensable. Let's explore how to test, debug, and optimize regular expressions like a pro.

Why Regex Testing Matters

The Regex Problem

// The classic regex pain point
const attempt1 = /^[a-z]+$/;           // Forgot case insensitive
const attempt2 = /^[a-zA-Z]+$/;        // Forgot numbers
const attempt3 = /^[a-zA-Z0-9]+$/;     // Forgot special chars
const attempt4 = /^[a-zA-Z0-9_-]+$/;   // Finally works!

// How many tries did it take? Too many.
// How many bugs shipped? Probably some.

const realityOfRegex = {
  firstTry: '20% success rate',
  debuggingTime: '30 minutes average',
  edgeCasesBugs: 'Countless',
  productionIncidents: 'More than you want',

  solution: 'Test your regex thoroughly before deploying'
};

console.log('Regex: Easy to write, hard to get right');
Enter fullscreen mode Exit fullscreen mode

Real-World Impact

// Email validation gone wrong
const badEmailRegex = /^.+@.+\..+$/;

// Looks reasonable, but accepts:
badEmailRegex.test('invalid..email@test..com');     // true (shouldn't be)
badEmailRegex.test('spaces are@allowed.com');       // true (shouldn't be)
badEmailRegex.test('@nodomain.com');                // true (shouldn't be)

// Better email regex (RFC 5322 simplified)
const goodEmailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;

// But how do you know it's better? TEST IT!

const testEmails = [
  { email: 'valid@example.com', shouldMatch: true },
  { email: 'user.name@example.com', shouldMatch: true },
  { email: 'user+tag@example.co.uk', shouldMatch: true },
  { email: 'invalid..email@test.com', shouldMatch: false },
  { email: '@nodomain.com', shouldMatch: false },
  { email: 'no-at-sign.com', shouldMatch: false },
  { email: 'spaces in@email.com', shouldMatch: false }
];

testEmails.forEach(({ email, shouldMatch }) => {
  const matches = goodEmailRegex.test(email);
  const status = matches === shouldMatch ? '' : '';
  console.log(`${status} ${email}: ${matches} (expected: ${shouldMatch})`);
});
Enter fullscreen mode Exit fullscreen mode

Essential Regex Patterns

1. Email Validation

// Various email validation approaches
const emailPatterns = {
  simple: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
  // Accepts: user@example.com
  // Rejects: spaces, multiple @, no domain

  standard: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
  // More strict, common use case

  rfc5322: /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
  // RFC 5322 compliant (simplified)
};

// Test function
function testEmailRegex(pattern, testCases) {
  console.log(`\nTesting pattern: ${pattern}\n`);

  testCases.forEach(({ email, valid }) => {
    const matches = pattern.test(email);
    const status = matches === valid ? '' : '';
    console.log(`${status} ${email.padEnd(30)} ${matches ? 'VALID' : 'INVALID'}`);
  });
}

// Test cases
const emailTests = [
  { email: 'simple@example.com', valid: true },
  { email: 'user.name+tag@example.co.uk', valid: true },
  { email: 'user@subdomain.example.com', valid: true },
  { email: 'invalid', valid: false },
  { email: '@nodomain.com', valid: false },
  { email: 'user@', valid: false },
  { email: 'user name@example.com', valid: false }
];

testEmailRegex(emailPatterns.standard, emailTests);
Enter fullscreen mode Exit fullscreen mode

2. Password Validation

// Password strength patterns
const passwordPatterns = {
  weak: /^.{8,}$/,
  // At least 8 characters

  medium: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/,
  // 8+ chars, 1 lowercase, 1 uppercase, 1 number

  strong: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/,
  // 8+ chars, 1 lowercase, 1 uppercase, 1 number, 1 special char

  veryStrong: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{12,}$/
  // 12+ chars with all requirements
};

function validatePassword(password) {
  const checks = {
    length: password.length >= 8,
    lowercase: /[a-z]/.test(password),
    uppercase: /[A-Z]/.test(password),
    number: /\d/.test(password),
    special: /[@$!%*?&]/.test(password)
  };

  const score = Object.values(checks).filter(Boolean).length;

  let strength;
  if (score <= 2) strength = 'Weak';
  else if (score === 3) strength = 'Medium';
  else if (score === 4) strength = 'Strong';
  else strength = 'Very Strong';

  return { checks, score, strength };
}

// Test passwords
const passwords = [
  'password',           // Weak
  'Password',           // Weak
  'Password123',        // Medium
  'Password123!',       // Strong
  'MyP@ssw0rd2024!'    // Very Strong
];

console.log('\nPassword Strength Analysis:\n');
passwords.forEach(pwd => {
  const result = validatePassword(pwd);
  console.log(`${pwd.padEnd(20)}${result.strength} (score: ${result.score}/5)`);
});
Enter fullscreen mode Exit fullscreen mode

3. URL Validation

// URL pattern matching
const urlPattern = /^(https?:\/\/)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/;

// Extract URL components
const urlComponentsPattern = /^(https?):\/\/([^\/]+)(\/.*)?$/;

function parseURL(url) {
  const match = url.match(urlComponentsPattern);

  if (!match) {
    return { valid: false };
  }

  return {
    valid: true,
    protocol: match[1],
    domain: match[2],
    path: match[3] || '/'
  };
}

// Test URLs
const urls = [
  'https://example.com',
  'http://subdomain.example.com/path',
  'https://example.com/path?query=value',
  'ftp://example.com',           // Invalid (wrong protocol)
  'not-a-url',                   // Invalid
  'http://example'               // Invalid (no TLD)
];

console.log('\nURL Validation:\n');
urls.forEach(url => {
  const result = parseURL(url);
  console.log(`${url.padEnd(40)}${result.valid ? '✓ Valid' : '✗ Invalid'}`);
  if (result.valid) {
    console.log(`  Protocol: ${result.protocol}, Domain: ${result.domain}, Path: ${result.path}\n`);
  }
});
Enter fullscreen mode Exit fullscreen mode

4. Phone Number Validation

// International phone number patterns
const phonePatterns = {
  us: /^(\+1|1)?[-.\s]?\(?([0-9]{3})\)?[-.\s]?([0-9]{3})[-.\s]?([0-9]{4})$/,
  // US: (555) 123-4567, 555-123-4567, +1-555-123-4567

  international: /^\+?[1-9]\d{1,14}$/,
  // E.164 format: +1234567890

  flexible: /^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$/
  // Flexible format
};

function formatUSPhone(phone) {
  const match = phone.match(phonePatterns.us);

  if (!match) return null;

  return `(${match[2]}) ${match[3]}-${match[4]}`;
}

// Test phone numbers
const phoneNumbers = [
  '5551234567',
  '(555) 123-4567',
  '+1-555-123-4567',
  '555.123.4567',
  '1-555-123-4567',
  'invalid',
  '123'
];

console.log('\nPhone Number Validation:\n');
phoneNumbers.forEach(phone => {
  const isValid = phonePatterns.us.test(phone);
  const formatted = formatUSPhone(phone);
  console.log(`${phone.padEnd(20)}${isValid ? '' : ''} ${formatted || 'Invalid'}`);
});
Enter fullscreen mode Exit fullscreen mode

5. Date Validation

// Date format patterns
const datePatterns = {
  iso8601: /^\d{4}-\d{2}-\d{2}$/,
  // 2024-01-31

  usDate: /^(0[1-9]|1[0-2])\/(0[1-9]|[12]\d|3[01])\/\d{4}$/,
  // MM/DD/YYYY: 01/31/2024

  europeanDate: /^(0[1-9]|[12]\d|3[01])\/(0[1-9]|1[0-2])\/\d{4}$/,
  // DD/MM/YYYY: 31/01/2024

  timestamp: /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?$/
  // ISO 8601 timestamp: 2024-01-31T12:34:56.789Z
};

function parseDate(dateString) {
  // Try ISO format
  if (datePatterns.iso8601.test(dateString)) {
    const [year, month, day] = dateString.split('-').map(Number);
    return { year, month, day, format: 'ISO 8601' };
  }

  // Try US format
  if (datePatterns.usDate.test(dateString)) {
    const [month, day, year] = dateString.split('/').map(Number);
    return { year, month, day, format: 'US (MM/DD/YYYY)' };
  }

  // Try European format
  if (datePatterns.europeanDate.test(dateString)) {
    const [day, month, year] = dateString.split('/').map(Number);
    return { year, month, day, format: 'European (DD/MM/YYYY)' };
  }

  return null;
}

// Test dates
const dates = [
  '2024-01-31',
  '01/31/2024',
  '31/01/2024',
  '2024-01-31T12:34:56Z',
  'invalid-date',
  '13/32/2024'  // Invalid month/day
];

console.log('\nDate Parsing:\n');
dates.forEach(date => {
  const parsed = parseDate(date);
  if (parsed) {
    console.log(`${date.padEnd(25)} → ✓ ${parsed.format}`);
    console.log(`  Year: ${parsed.year}, Month: ${parsed.month}, Day: ${parsed.day}\n`);
  } else {
    console.log(`${date.padEnd(25)} → ✗ Invalid\n`);
  }
});
Enter fullscreen mode Exit fullscreen mode

Advanced Regex Techniques

1. Lookahead and Lookbehind

// Positive lookahead: (?=...)
const hasNumberLookahead = /^(?=.*\d).+$/;
console.log(hasNumberLookahead.test('password123'));  // true
console.log(hasNumberLookahead.test('password'));     // false

// Negative lookahead: (?!...)
const noNumberLookahead = /^(?!.*\d).+$/;
console.log(noNumberLookahead.test('password'));      // true
console.log(noNumberLookahead.test('password123'));   // false

// Positive lookbehind: (?<=...)
const afterDollar = /(?<=\$)\d+/;
console.log('$100'.match(afterDollar));  // ['100']
console.log('100'.match(afterDollar));   // null

// Negative lookbehind: (?<!...)
const notAfterDollar = /(?<!\$)\d+/;
console.log('$100'.match(notAfterDollar));  // null
console.log('100'.match(notAfterDollar));   // ['100']

// Complex example: Password must have number but not start with number
const complexPassword = /^(?=.*\d)(?!\d).{8,}$/;
console.log('\nComplex Password Validation:');
console.log(complexPassword.test('password123'));   // true
console.log(complexPassword.test('123password'));   // false (starts with number)
console.log(complexPassword.test('password'));      // false (no number)
Enter fullscreen mode Exit fullscreen mode

2. Named Capture Groups

// Named groups for better readability
const emailPattern = /^(?<user>[a-zA-Z0-9._%+-]+)@(?<domain>[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})$/;

function parseEmail(email) {
  const match = email.match(emailPattern);

  if (!match) return null;

  return {
    user: match.groups.user,
    domain: match.groups.domain,
    full: email
  };
}

const emailInfo = parseEmail('john.doe@example.com');
console.log('\nEmail Parsing with Named Groups:');
console.log(emailInfo);
// { user: 'john.doe', domain: 'example.com', full: 'john.doe@example.com' }

// URL parsing with named groups
const urlPattern2 = /^(?<protocol>https?):\/\/(?<domain>[^\/]+)(?<path>\/.*)?$/;

function parseURL2(url) {
  const match = url.match(urlPattern2);
  if (!match) return null;

  return match.groups;
}

console.log('\nURL Parsing:');
console.log(parseURL2('https://example.com/path'));
// { protocol: 'https', domain: 'example.com', path: '/path' }
Enter fullscreen mode Exit fullscreen mode

3. Greedy vs Non-Greedy

// Greedy matching (default)
const greedyPattern = /<.*>/;
const html = '<div>Content</div><span>More</span>';
console.log('\nGreedy Match:');
console.log(html.match(greedyPattern)[0]);
// Output: '<div>Content</div><span>More</span>' (matches everything)

// Non-greedy matching (add ?)
const nonGreedyPattern = /<.*?>/;
console.log('\nNon-Greedy Match:');
console.log(html.match(nonGreedyPattern)[0]);
// Output: '<div>' (matches shortest possible)

// Extract all HTML tags
const allTags = html.match(/<.*?>/g);
console.log('\nAll Tags:');
console.log(allTags);
// ['<div>', '</div>', '<span>', '</span>']
Enter fullscreen mode Exit fullscreen mode

4. Regex for Data Extraction

// Extract data from log files
const logPattern = /^\[(?<timestamp>.*?)\] (?<level>\w+): (?<message>.*)$/;

const logLines = [
  '[2024-01-31 12:34:56] INFO: Server started',
  '[2024-01-31 12:35:10] ERROR: Connection failed',
  '[2024-01-31 12:35:15] WARN: High memory usage'
];

console.log('\nLog Parsing:\n');
logLines.forEach(line => {
  const match = line.match(logPattern);
  if (match) {
    const { timestamp, level, message } = match.groups;
    console.log(`[${level}] ${timestamp}`);
    console.log(`  Message: ${message}\n`);
  }
});

// Extract prices from text
const pricePattern = /\$(\d+(?:,\d{3})*(?:\.\d{2})?)/g;
const text = 'The laptop costs $1,299.99 and the mouse costs $29.99';
const prices = text.match(pricePattern);
console.log('Prices found:', prices);
// ['$1,299.99', '$29.99']

// Extract hashtags
const hashtagPattern = /#[\w]+/g;
const tweet = 'Learning #JavaScript and #Regex today! #webdev';
const hashtags = tweet.match(hashtagPattern);
console.log('Hashtags:', hashtags);
// ['#JavaScript', '#Regex', '#webdev']
Enter fullscreen mode Exit fullscreen mode

Implementation: Regex Testing Tool

1. Node.js Regex Tester

const readline = require('readline');

class RegexTester {
  constructor() {
    this.history = [];
  }

  test(pattern, testString, flags = '') {
    try {
      const regex = new RegExp(pattern, flags);

      const result = {
        pattern,
        flags,
        testString,
        matches: regex.test(testString),
        match: testString.match(regex),
        matchAll: [...testString.matchAll(new RegExp(pattern, flags + 'g'))],
        groups: null,
        timestamp: new Date()
      };

      // Extract groups if present
      const match = testString.match(regex);
      if (match && match.groups) {
        result.groups = match.groups;
      }

      this.history.push(result);
      return result;
    } catch (error) {
      return {
        error: error.message,
        pattern,
        testString
      };
    }
  }

  printResult(result) {
    if (result.error) {
      console.log(`\n❌ Error: ${result.error}\n`);
      return;
    }

    console.log('\n' + '='.repeat(60));
    console.log('REGEX TEST RESULT');
    console.log('='.repeat(60));
    console.log(`Pattern: /${result.pattern}/${result.flags}`);
    console.log(`Test String: "${result.testString}"`);
    console.log(`\nMatches: ${result.matches ? '✓ YES' : '✗ NO'}`);

    if (result.match) {
      console.log('\nMatch Details:');
      console.log(`  Full Match: "${result.match[0]}"`);

      if (result.match.length > 1) {
        console.log('  Capture Groups:');
        for (let i = 1; i < result.match.length; i++) {
          console.log(`    [${i}]: "${result.match[i]}"`);
        }
      }

      if (result.groups) {
        console.log('  Named Groups:');
        Object.entries(result.groups).forEach(([name, value]) => {
          console.log(`    ${name}: "${value}"`);
        });
      }
    }

    if (result.matchAll.length > 1) {
      console.log(`\nGlobal Matches (${result.matchAll.length}):`);
      result.matchAll.forEach((match, i) => {
        console.log(`  [${i}]: "${match[0]}"`);
      });
    }

    console.log('='.repeat(60) + '\n');
  }

  benchmark(pattern, testString, flags = '', iterations = 10000) {
    const regex = new RegExp(pattern, flags);

    const start = Date.now();
    for (let i = 0; i < iterations; i++) {
      regex.test(testString);
    }
    const duration = Date.now() - start;

    console.log(`\nBenchmark Results:`);
    console.log(`  Iterations: ${iterations.toLocaleString()}`);
    console.log(`  Total Time: ${duration}ms`);
    console.log(`  Average: ${(duration / iterations * 1000).toFixed(3)}μs per test`);
    console.log(`  Rate: ${(iterations / duration * 1000).toFixed(0)} tests/second\n`);
  }
}

// Usage
const tester = new RegexTester();

// Test email
const emailResult = tester.test(
  '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$',
  'user@example.com'
);
tester.printResult(emailResult);

// Test with named groups
const urlResult = tester.test(
  '^(?<protocol>https?)://(?<domain>[^/]+)(?<path>/.*)?$',
  'https://example.com/path'
);
tester.printResult(urlResult);

// Benchmark
tester.benchmark(
  '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$',
  'user@example.com',
  '',
  100000
);
Enter fullscreen mode Exit fullscreen mode

2. Express API for Regex Testing

const express = require('express');
const app = express();

app.use(express.json());

app.post('/api/regex/test', (req, res) => {
  const { pattern, testString, flags = '' } = req.body;

  if (!pattern || testString === undefined) {
    return res.status(400).json({
      error: 'Pattern and testString required'
    });
  }

  try {
    const regex = new RegExp(pattern, flags);

    const matches = regex.test(testString);
    const match = testString.match(regex);
    const matchAll = [...testString.matchAll(new RegExp(pattern, flags + 'g'))];

    res.json({
      success: true,
      pattern: `/${pattern}/${flags}`,
      testString,
      matches,
      match: match ? {
        fullMatch: match[0],
        groups: match.slice(1),
        namedGroups: match.groups || null,
        index: match.index
      } : null,
      matchAll: matchAll.map(m => ({
        match: m[0],
        index: m.index,
        groups: m.slice(1)
      }))
    });
  } catch (error) {
    res.status(400).json({
      success: false,
      error: error.message
    });
  }
});

app.post('/api/regex/validate', (req, res) => {
  const { pattern } = req.body;

  try {
    new RegExp(pattern);
    res.json({ valid: true });
  } catch (error) {
    res.json({ 
      valid: false, 
      error: error.message 
    });
  }
});

app.post('/api/regex/extract', (req, res) => {
  const { pattern, text, flags = 'g' } = req.body;

  try {
    const regex = new RegExp(pattern, flags);
    const matches = [...text.matchAll(regex)];

    res.json({
      success: true,
      count: matches.length,
      matches: matches.map(m => m[0]),
      details: matches.map(m => ({
        match: m[0],
        index: m.index,
        groups: m.slice(1)
      }))
    });
  } catch (error) {
    res.status(400).json({
      success: false,
      error: error.message
    });
  }
});

app.listen(3000, () => {
  console.log('Regex API running on port 3000');
  console.log('POST /api/regex/test - Test regex pattern');
  console.log('POST /api/regex/validate - Validate regex syntax');
  console.log('POST /api/regex/extract - Extract all matches');
});
Enter fullscreen mode Exit fullscreen mode

3. Quick Online Regex Testing

For rapid pattern development and debugging, using a regex tester can speed up your workflow significantly. This is particularly useful when:

  • Learning regex: Visual feedback helps understand patterns
  • Debugging patterns: See exactly what matches and why
  • Testing edge cases: Quickly validate against multiple test strings
  • Sharing patterns: Get shareable links for team collaboration

Once you've perfected your pattern, integrate it into your codebase with confidence.

Common Regex Pitfalls

1. Catastrophic Backtracking

// Dangerous pattern - can hang with certain inputs
const dangerousPattern = /^(a+)+$/;

// This will be VERY slow:
// dangerousPattern.test('aaaaaaaaaaaaaaaaaaaaaaX');

// Better pattern:
const safePattern = /^a+$/;

// Example of exponential complexity
function demonstrateBacktracking() {
  const pattern = /(a+)+b/;

  for (let len = 10; len <= 25; len += 5) {
    const input = 'a'.repeat(len) + 'X';  // No 'b' at end

    console.time(`Length ${len}`);
    try {
      pattern.test(input);
    } catch (e) {
      console.log('Timed out');
    }
    console.timeEnd(`Length ${len}`);
  }
}

// Avoid nested quantifiers: (a+)+, (a*)*, (a+)*
console.log('⚠️  Avoid catastrophic backtracking');
Enter fullscreen mode Exit fullscreen mode

2. Unescaped Special Characters

// Wrong: Special chars not escaped
const wrong = /example.com/;  // . matches ANY character
wrong.test('exampleXcom');    // true (oops!)

// Right: Escape special characters
const right = /example\.com/;
right.test('exampleXcom');     // false
right.test('example.com');     // true

// Characters that need escaping: . * + ? ^ $ { } ( ) | [ ] \ /
const specialChars = /[\.\*\+\?\^\$\{\}\(\)\|\[\]\\\/]/;

function escapeRegex(string) {
  return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

console.log(escapeRegex('example.com'));  // 'example\\.com'
Enter fullscreen mode Exit fullscreen mode

3. Forgetting Case Sensitivity

// Case sensitive by default
const caseSensitive = /hello/;
console.log(caseSensitive.test('Hello'));  // false

// Use 'i' flag for case insensitive
const caseInsensitive = /hello/i;
console.log(caseInsensitive.test('Hello'));  // true

// Common mistake in email validation
const badEmail = /^[a-z0-9]+@[a-z0-9]+\.[a-z]+$/;  // Rejects uppercase!
const goodEmail = /^[a-zA-Z0-9]+@[a-zA-Z0-9]+\.[a-zA-Z]+$/;  // Better
const bestEmail = /^[a-z0-9]+@[a-z0-9]+\.[a-z]+$/i;  // Best (use flag)
Enter fullscreen mode Exit fullscreen mode

Testing Strategies

// Comprehensive test suite for regex patterns
class RegexTestSuite {
  constructor(pattern, flags = '') {
    this.pattern = pattern;
    this.flags = flags;
    this.regex = new RegExp(pattern, flags);
    this.tests = [];
  }

  addTest(input, shouldMatch, description = '') {
    this.tests.push({ input, shouldMatch, description });
    return this;
  }

  run() {
    console.log(`\nTesting: /${this.pattern}/${this.flags}\n`);

    let passed = 0;
    let failed = 0;

    this.tests.forEach((test, i) => {
      const matches = this.regex.test(test.input);
      const success = matches === test.shouldMatch;

      if (success) {
        passed++;
        console.log(`✓ Test ${i + 1}: ${test.description || test.input}`);
      } else {
        failed++;
        console.log(`✗ Test ${i + 1}: ${test.description || test.input}`);
        console.log(`  Expected: ${test.shouldMatch}, Got: ${matches}`);
      }
    });

    console.log(`\nResults: ${passed} passed, ${failed} failed\n`);
    return { passed, failed, total: this.tests.length };
  }
}

// Example: Email validation test suite
const emailSuite = new RegexTestSuite(
  '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$'
);

emailSuite
  .addTest('simple@example.com', true, 'Simple valid email')
  .addTest('user.name@example.com', true, 'Email with dot')
  .addTest('user+tag@example.com', true, 'Email with plus')
  .addTest('user@subdomain.example.com', true, 'Subdomain')
  .addTest('invalid', false, 'No @ symbol')
  .addTest('@nodomain.com', false, 'No username')
  .addTest('user@', false, 'No domain')
  .addTest('user name@example.com', false, 'Space in username')
  .run();
Enter fullscreen mode Exit fullscreen mode

Performance Optimization

// Benchmark different patterns
function benchmarkPatterns(patterns, testString, iterations = 100000) {
  console.log(`\nBenchmarking ${iterations.toLocaleString()} iterations\n`);

  Object.entries(patterns).forEach(([name, pattern]) => {
    const regex = new RegExp(pattern);

    const start = Date.now();
    for (let i = 0; i < iterations; i++) {
      regex.test(testString);
    }
    const duration = Date.now() - start;

    console.log(`${name.padEnd(20)}: ${duration}ms (${(iterations / duration * 1000).toFixed(0)} ops/sec)`);
  });
}

// Compare email patterns
const emailPatterns2 = {
  'Simple': '^.+@.+\\..+$',
  'Standard': '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$',
  'Complex': '^[a-zA-Z0-9.!#$%&\'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$'
};

benchmarkPatterns(emailPatterns2, 'user@example.com');

// Result: Simpler patterns are faster, but less accurate
// Balance correctness with performance
Enter fullscreen mode Exit fullscreen mode

Conclusion: Master Regex Through Testing

Regular expressions are powerful but unforgiving. The difference between a working regex and a production incident is thorough testing. Whether you're validating user input, parsing logs, or transforming data, test your patterns extensively before deploying.

Test edge cases (empty, special chars, unicode)

Benchmark performance (avoid catastrophic backtracking)

Use appropriate flags (i for case-insensitive, g for global)

Escape special characters (. * + ? etc.)

Named groups for clarity (better than numbered groups)

Start simple, iterate (don't over-engineer)

Document complex patterns (future you will thank you)

Share test cases (team collaboration)

Regex Testing Checklist:

[ ] Test valid inputs (should match)
[ ] Test invalid inputs (should not match)
[ ] Test edge cases (empty, very long, special chars)
[ ] Test Unicode if needed
[ ] Benchmark performance
[ ] Check for catastrophic backtracking
[ ] Verify all capture groups work
[ ] Test with real production data
[ ] Document pattern purpose and examples
Enter fullscreen mode Exit fullscreen mode

The Bottom Line:
Regex is like a chainsaw—incredibly useful when used correctly, dangerous when used carelessly. Test your patterns thoroughly, understand what they match (and don't match), and always have examples documented. Your future self debugging a production issue at 2am will thank you.


What's your most complex regex pattern? Share your regex war stories in the comments!

webdev #regex #testing #javascript #programming

Top comments (0)