JavaScript Phone Number Validation: Why 123-345-34567 Passes Your Regex (And How to Fix It)

Phone number validation is a cornerstone of user input handling in web development. Whether you’re building a checkout form, a contact page, or a user registration system, ensuring users enter valid phone numbers is critical for data integrity, communication reliability, and user experience. Yet, many developers rely on simplistic regular expressions (regex) that inadvertently allow invalid numbers—like 123-345-34567—to slip through.

In this blog, we’ll demystify why common regex patterns fail, break down the flaws in these patterns, and provide a step-by-step guide to creating robust phone number validation in JavaScript. By the end, you’ll understand how to stop invalid numbers in their tracks and ensure your forms collect only usable, accurate data.

Table of Contents#

  1. Understanding the Problem: Why Does 123-345-34567 Pass?
  2. Common Regex Pitfalls in Phone Number Validation
  3. Breaking Down the Invalid Number: 123-345-34567
  4. How to Fix Your Regex: A Step-by-Step Guide
  5. Advanced Validation: Beyond Basic Regex
  6. Testing Your Validation: Ensure It Works
  7. Conclusion
  8. References

1. Understanding the Problem: Why Does 123-345-34567 Pass?#

Let’s start with the elephant in the room: Why does 123-345-34567—a clearly invalid phone number—pass many developers’ regex checks?

Consider this common (but flawed) regex pattern, often used to validate "US-style" phone numbers:

const flawedRegex = /\d{3}-\d{3}-\d+/; // Matches "3 digits - 3 digits - 1+ digits"

If you test 123-345-34567 against this regex, it returns true. Why? Because the regex only checks for:

  • 3 digits (\d{3}) followed by a hyphen,
  • Another 3 digits (\d{3}) followed by a hyphen,
  • One or more digits (\d+) at the end.

The problem? 34567 has 5 digits in the final segment—more than the standard 4 digits for US phone numbers (e.g., 123-456-7890). But since \d+ allows 1 or more digits, the regex doesn’t enforce an upper limit. Even worse, there’s no start (^) or end ($) anchor, so the regex might match partial strings (e.g., 123-345-34567xyz would also pass).

2. Common Regex Pitfalls in Phone Number Validation#

To understand why invalid numbers slip through, let’s identify the most frequent regex mistakes developers make:

Pitfall 1: Missing Start/End Anchors (^ and $)#

Without ^ (start of string) and $ (end of string), the regex matches any substring that fits the pattern—even if extra characters exist before or after. For example:

  • Flawed: /\d{3}-\d{3}-\d{4}/ (matches 123-456-7890 in abc123-456-7890def).
  • Fixed: /^\d{3}-\d{3}-\d{4}$/ (only matches 123-456-7890 exactly).

Pitfall 2: Overly Permissive Quantifiers#

Using + (1 or more) or * (0 or more) instead of fixed quantifiers (e.g., {4} for exactly 4 digits) allows invalid lengths. For example:

  • Flawed: /\d{3}-\d{3}-\d+/ (allows 1–infinite digits in the final segment).
  • Fixed: /\d{3}-\d{3}-\d{4}/ (enforces exactly 4 digits).

Pitfall 3: Ignoring Valid Separators (or Allowing Invalid Ones)#

Phone numbers use diverse separators: hyphens (-), dots (.), spaces ( ), or parentheses (e.g., (123) 456-7890). A regex that only allows hyphens will reject valid formats, while one that allows any separator may accept invalid ones (e.g., 123_456_7890).

Pitfall 4: Forgetting Country Codes#

International numbers (e.g., +44 20 7946 0958 for the UK) require handling country codes. A regex that only validates US numbers will reject global formats.

Pitfall 5: Not Escaping Special Characters#

Characters like . (wildcard) or ( (grouping) must be escaped (\., \() if used literally. For example, /\d{3}.\d{3}.\d{4}/ would match 123x456y7890 (since . matches any character), not just 123.456.7890.

3. Breaking Down the Invalid Number: 123-345-34567#

Let’s dissect 123-345-34567 to see why it’s invalid and how a flawed regex might accept it:

Structure of a Valid US Phone Number#

A standard US phone number has 10 digits, typically formatted as:
[Area Code]-[Central Office Code]-[Line Number]

  • Area Code: 3 digits (e.g., 123),
  • Central Office Code: 3 digits (e.g., 456),
  • Line Number: 4 digits (e.g., 7890).

Why 123-345-34567 Is Invalid#

  • Total digits: 3 + 3 + 5 = 11 (valid US numbers have 10).
  • Final segment: 5 digits (should be 4).

Why a Flawed Regex Accepts It#

If your regex is:

const badRegex = /\d{3}-\d{3}-\d+/; // No anchors, uses \d+
  • 123-345- matches the first two segments (\d{3}-\d{3}-),
  • 34567 matches \d+ (1+ digits),
  • No ^/$ means extra digits are ignored.

4. How to Fix Your Regex: A Step-by-Step Guide#

Let’s build a robust regex for US phone numbers, then expand to international formats.

Step 1: Enforce Exact Length with Anchors and Fixed Quantifiers#

Start with the simplest valid case: 123-456-7890 (hyphen-separated, 10 digits).

Regex:

const usPhoneRegex = /^\d{3}-\d{3}-\d{4}$/;

Explanation:

  • ^ = Start of string.
  • \d{3} = Exactly 3 digits (area code).
  • - = Literal hyphen.
  • \d{3} = Exactly 3 digits (central office code).
  • - = Literal hyphen.
  • \d{4} = Exactly 4 digits (line number).
  • $ = End of string.

Test:

  • Valid: 123-456-7890true.
  • Invalid: 123-345-34567false (5 digits in final segment).

Step 2: Support Multiple Separators#

Users may enter numbers with spaces, dots, or parentheses (e.g., (123) 456-7890, 123.456.7890). Update the regex to allow these:

Regex:

const flexibleSeparatorRegex = /^\(\d{3}\) [-.]?\d{3}[-.]?\d{4}$|^\d{3}[-.]?\d{3}[-.]?\d{4}$/;

Explanation:

  • | = OR (supports two formats: (123) 456-7890 or 123-456-7890).
  • \( and \) = Literal parentheses for area codes like (123).
  • [-.]? = Optional hyphen or dot separator (the ? makes it optional).

Test:

  • Valid: (123) 456-7890, 123.456.7890, 1234567890 (no separators).
  • Invalid: 123_456_7890 (underscores not allowed).

Step 3: Add Optional Country Codes (International Support)#

For global apps, allow optional country codes (e.g., +1 for US, +44 for UK):

Regex:

const internationalRegex = /^(\+\d{1,2}\s?)?(\(\d{3}\)|\d{3})[-.\s]?\d{3}[-.\s]?\d{4}$/;

Explanation:

  • (\+\d{1,2}\s?)? = Optional country code: + followed by 1–2 digits (e.g., +1), optional space (\s?).
  • (\(\d{3}\)|\d{3}) = Area code as (123) or 123.
  • [-.\s]? = Allow hyphens, dots, or spaces as separators.

Test:

  • Valid: +1 123-456-7890, +44 (123) 456-7890.

5. Advanced Validation: Beyond Basic Regex#

Regex alone has limits: it can’t validate area code legitimacy (e.g., 911 is invalid in the US) or international format nuances (e.g., varying lengths for country codes). For production apps, use a library like libphonenumber-js (Google’s phone number validation library, ported to JavaScript).

Why Use a Library?#

  • Handles edge cases: Validates area codes, country-specific formats, and even mobile vs. landline.
  • International support: Covers 200+ countries.
  • Normalization: Converts numbers to E.164 format (e.g., +1234567890).

Example with libphonenumber-js#

  1. Install:

    npm install libphonenumber-js
  2. Validate a number:

    import { parsePhoneNumberFromString } from 'libphonenumber-js';
     
    function isValidPhoneNumber(number, country = 'US') {
      const phoneNumber = parsePhoneNumberFromString(number, country);
      return phoneNumber?.isValid() || false;
    }
     
    // Test
    console.log(isValidPhoneNumber('123-345-34567')); // false (invalid US number)
    console.log(isValidPhoneNumber('+1 123-456-7890')); // true (valid US number)
    console.log(isValidPhoneNumber('+44 20 7946 0958', 'GB')); // true (valid UK number)

6. Testing Your Validation#

Always test with a suite of cases to ensure your regex or library works:

Test Cases to Cover#

CaseExpected Result
123-456-7890Valid
123-345-34567Invalid
(123) 456-7890Valid
123.456.7890Valid
1234567890Valid
+1 123-456-7890Valid
123-45-67890Invalid (2-digit central office code)
123-456-78901Invalid (11 total digits)

JavaScript Test Script#

const testCases = [
  { input: '123-456-7890', expected: true },
  { input: '123-345-34567', expected: false },
  { input: '(123) 456-7890', expected: true },
  { input: '123.456.7890', expected: true },
  { input: '1234567890', expected: true },
  { input: '+1 123-456-7890', expected: true },
  { input: '123-45-67890', expected: false },
  { input: '123-456-78901', expected: false },
];
 
// Using the international regex from Step 3
const regex = /^(\+\d{1,2}\s?)?(\(\d{3}\)|\d{3})[-.\s]?\d{3}[-.\s]?\d{4}$/;
 
testCases.forEach(({ input, expected }) => {
  const result = regex.test(input);
  console.log(`${input}: ${result === expected ? 'PASS' : 'FAIL'}`);
});

7. Conclusion#

Phone number validation is deceptively complex. A regex like /\d{3}-\d{3}-\d+/ fails because it lacks anchors, uses permissive quantifiers, and ignores real-world formats. By fixing these issues—adding ^/$, enforcing fixed digit counts, and supporting valid separators—you can block invalid numbers like 123-345-34567.

For global apps or strict validation, use libraries like libphonenumber-js to handle edge cases regex can’t. Always test rigorously with valid and invalid inputs to ensure reliability.

8. References#