Understanding US Area Codes & North American Numbering Plan (NANP)

Published: July 10, 2026 | Author: Sarah Jenkins (Telecom API Architect) | Category: Telecommunications

A deep dive into how American telephone numbers are structured under the North American Numbering Plan (NANP) for developer form validation.


Every US phone number is NXX-NXX-XXXX where N is 2–9 and X is 0–9. That one rule, plus knowing which ranges are reserved, is most of what a validator needs — and the reason +1 is not a US-only prefix is the part that breaks production systems.

The format, digit by digit

A North American number is ten digits in three parts:

      NPA        NXX        XXXX
    ( 2 1 2 ) -  5 5 5  -  0 1 4 7
      │            │          │
      │            │          └─ line number: 0000-9999, no constraint
      │            └──────────── central office code: N=2-9, then any two digits
      └───────────────────────── area code: N=2-9, then any two digits

Why N is 2–9 and never 0 or 1. In the original plan, 1 was the trunk prefix that told a switch "the digits after this are long distance", and 0 reached the operator. A number whose first digit was 0 or 1 could not be distinguished from an access code by a mechanical switch reading digits as they arrived. Both the area code and the central office code therefore start at 2. The constraint outlived the switches: it is now in the plan itself, and a number beginning 1 or 0 in either position is not a valid NANP number at all.

Two further exclusions matter for validation:

  • N11 central office codes are reserved for services. 211, 311, 411, 511, 611, 711, 811 and 911 are service codes, so no central office code has 11 as its second and third digits.
  • NPA second and third digits were once restricted. Before interchangeable codes arrived in 1995, area codes had 0 or 1 as the middle digit (N0X/N1X) and central office codes never did. That distinction is gone — 480, 630 and 847 are all post-1995 area codes with middle digits that would have been illegal before — so any validator still enforcing the old N0X/N1X rule rejects valid numbers.

555 is not a free-for-all

The widely repeated claim that "555 numbers are fake" is wrong in a way that matters if you use them in test fixtures that later touch a dialler.

Only 555-0100 through 555-0199 is reserved. NANPA's own 555 line numbers page states that these fictitious, non-working numbers remain reserved for entertainment and advertising use. That is 100 numbers per area code.

Everything else in the 555 central office code is assignable to carriers, with two national-use exceptions: 555-1212 (directory assistance) and 555-4334. So 212-555-0147 is safe in a test fixture forever; 212-555-8820 may ring somebody's phone.

If your fixtures need a number that can never connect, constrain the line number to 0100–0199:

(NPA) 555-01XX    where XX is 00-99

+1 is not the United States

This is the assumption that produces the most expensive bugs. +1 is the country code for the *entire* North American Numbering Plan — 20 countries and territories, not one. A system that treats +1 as "domestic US" will mis-route, mis-price, or wrongly block numbers belonging to:

North AmericaUnited States (including Puerto Rico, US Virgin Islands, Guam, Northern Mariana Islands, American Samoa), Canada, Bermuda
CaribbeanAnguilla, Antigua and Barbuda, Bahamas, Barbados, British Virgin Islands, Cayman Islands, Dominica, Dominican Republic, Grenada, Jamaica, Montserrat, Saint Kitts and Nevis, Saint Lucia, Saint Vincent and the Grenadines, Sint Maarten, Trinidad and Tobago, Turks and Caicos Islands

Mexico is not in the NANP; it uses +52. Curaçao and the Caribbean Netherlands kept +599 when Sint Maarten migrated to +1.

Three consequences for code:

  1. You cannot infer country from +1. You need the area code. 246 is Barbados, 809/829/849 are the Dominican Republic, 868 is Trinidad and Tobago, 721 is Sint Maarten.
  2. Toll and regulatory rules differ by country, not by country code. A +1 SMS to 876 (Jamaica) is international traffic with international pricing, and in the US is subject to different consent rules than a domestic message.
  3. "Is this number US?" is a data lookup, not a regex. Any validator that answers it from the string alone is guessing.

Overlays killed geography

An area code used to mean a place. Two mechanisms broke that:

  • A split divides the existing geography. The old code keeps part of the territory, a new code takes the rest, and every number in the moved region changes. Splits are disruptive — every business reprints everything — so they have fallen out of favour.
  • An overlay adds a second area code over the *same* geography. Existing numbers keep their code; new numbers get the new one. Nobody has to change a number, which is why overlays are now the norm. The cost is mandatory ten-digit local dialling across the overlay area, because seven digits are no longer unique.

The practical result: in an overlay region, two houses on the same street can sit in different area codes, and both are correct. 212, 646, 332 and 917 all serve Manhattan. 213 and 323 overlay Los Angeles.

So:

  • Do not use area code to derive a city, a timezone, or a tax jurisdiction. Number portability compounds this — a number keeps its area code when its owner moves states.
  • Do use it to check that a number is plausible for a region in test data, which is a much weaker claim and the only one the data supports.

Portability, and the two NPA ranges that are not geographic

Local number portability means a number keeps its area code when its owner changes carrier, and in practice when they move house or move state. A 415 mobile may have been in Texas for a decade. Two rules follow:

  • You cannot derive the carrier from the number. The NPA-NXX block tells you which carrier the range was *allocated* to, not who serves that specific number today. Carrier identification needs a Local Routing Number lookup against portability data, which is a paid query and is stale the moment you cache it.
  • You cannot derive location, tax jurisdiction or timezone. Billing systems that infer sales tax from the area code have been wrong since 1996, and will be wrong more often every year.

Two NPA ranges are not geographic at all and need handling separately:

rangemeaning
800, 833, 844, 855, 866, 877, 888toll-free; the called party pays. Not tied to any place.
900premium rate; the caller is billed. Frequently blocked outright.

Also reserved: N9X codes ending in 9 are held for future expansion in some plans, 710 is a single government service code, and NPA values 37X and 96X are unassigned and held in reserve for a future expansion to more than ten digits.

The practical consequence for a form: if you block 900 numbers as a fraud signal — reasonable — do it as an explicit check and say so in the error, rather than letting a general-purpose regex reject them and leave the user guessing.

Validation you can paste

Structural validation only — this says a string could be a NANP number, not that it is assigned or reachable.

const NANP = /^(?:\+?1[\s.-]?)?\(?([2-9]\d{2})\)?[\s.-]?([2-9]\d{2})[\s.-]?(\d{4})$/;

function parseNanp(input) {
  const m = NANP.exec(String(input).trim());
  if (!m) return { valid: false, reason: 'not a NANP format' };

  const [, npa, nxx, line] = m;

  // N11 codes are service codes, never central office codes.
  if (nxx.endsWith('11')) return { valid: false, reason: `${nxx} is a service code` };

  return {
    valid: true,
    e164: `+1${npa}${nxx}${line}`,
    npa,
    nxx,
    line,
    // The only block guaranteed never to connect.
    fictional: nxx === '555' && line >= '0100' && line <= '0199'
  };
}

parseNanp('(212) 555-0147');
// { valid: true, e164: '+12125550147', npa: '212', nxx: '555', line: '0147', fictional: true }
parseNanp('112-555-0147');   // { valid: false, reason: 'not a NANP format' }
parseNanp('212-411-0147');   // { valid: false, reason: '411 is a service code' }
import re

NANP = re.compile(
    r"^(?:\+?1[\s.\-]?)?\(?([2-9]\d{2})\)?[\s.\-]?([2-9]\d{2})[\s.\-]?(\d{4})
quot; ) def parse_nanp(value: str) -> dict: m = NANP.match(value.strip()) if not m: return {"valid": False, "reason": "not a NANP format"} npa, nxx, line = m.groups() if nxx.endswith("11"): return {"valid": False, "reason": f"{nxx} is a service code"} return { "valid": True, "e164": f"+1{npa}{nxx}{line}", "npa": npa, "nxx": nxx, "line": line, "fictional": nxx == "555" and "0100" <= line <= "0199", } assert parse_nanp("212.555.0147")["e164"] == "+12125550147" assert parse_nanp("012-555-0147")["valid"] is False assert parse_nanp("212-911-0147")["valid"] is False

Store numbers in E.164 (+12125550147): no punctuation, no ambiguity, and it sorts and compares as a string. Format for display at the edge, never in the database.

For anything beyond structure — is this number assigned, which carrier holds it, is it mobile or landline — you need a lookup against carrier data. A regex cannot tell you, and any library claiming to is reading a bundled snapshot that ages.

Need numbers to test with: the phone number generator produces NANP-shaped numbers using the area codes assigned to each state, with the line number held inside the reserved fictional block.