Phone Numbers That Break Software: E.164 & Validation Edge Cases
Trunk prefixes that are not 0, a 15-digit cap, +1 shared by 20 countries, spreadsheets eating leading zeros: the phone number edge cases that break validation.
By VincentPublished September 4, 2026Last updated September 4, 202611 min read

Names taught us to distrust validation and birthdays taught us to distrust arithmetic. The third field in this series looks like the easiest of the three. A phone number is digits, and there is even an international standard, E.164, that says exactly how to write one. Yet phone-number code fails constantly, and it fails in a particular way. A name is stored and displayed, a birthday is computed against, but a phone number is interpreted: the same ten digits mean different things depending on which country's plan they belong to, whether the person typed the domestic or the international form, and what their spreadsheet did to the leading zero on the way in. Every phone validator is a small parser of the world's numbering plans, and the world's numbering plans were never designed to be parsed.
What E.164 actually specifies — and what it leaves out
E.164 is the ITU-T recommendation that defines the international public telecommunication numbering plan [itu-e164]. It says a number in international format is a plus sign, a country code of one to three digits, and the national significant number, and that the whole thing — country code included — may be at most 15 digits [e164-wiki]. No spaces, brackets or hyphens; no trunk prefix; no extension. That is the entire specification: a prefix, a maximum length, and a plus.
What E.164 does not specify is where the trouble lives. It sets no minimum length. It says nothing about which numbers exist or are assigned. It says nothing about how people in each country actually write their numbers, and it delegates the structure of the national part to roughly two hundred national plans that disagree about almost everything: whether numbers have a fixed length, whether the domestic form starts with a prefix that must be removed, and whether a leading zero is that prefix or a genuine digit. A validator that 'checks E.164' checks the envelope. Everything below opens the letter.
| Part | London (Ofcom drama range) | United States (fiction range) |
|---|---|---|
| Domestic form | 020 7946 0958 | (415) 555-0142 or 1-415-555-0142 |
| Trunk prefix (dropped) | 0 | none — the 1 is the country code |
| Country code | 44 | 1 |
| Area / destination code | 20 | 415 |
| Subscriber number | 7946 0958 | 555 0142 |
| E.164 | +442079460958 (12 digits) | +14155550142 (11 digits) |
| E.164 ceiling | 15 digits | 15 digits |
The edge cases that actually break systems
The leading zero is not always a prefix
Across most of Europe, Africa and Asia a domestic number starts with a 0 that is not part of the number: it is the trunk prefix, the digit that tells the exchange 'a full national number follows', and it is dropped when the number is written internationally. That is the rule everyone learns, and it is wrong for nearly a quarter of the countries our generator supports. Italy is the famous exception. Since 1998 the leading 0 of an Italian landline has been a permanent part of the number and is dialled from abroad too, so a Rome number written 06 6982 1234 is +39 06 6982 1234 — zero and all [italy-wiki]. Strip it and you produce a shorter, still plausible, different number. Mexico joined that group on 3 August 2019, when the regulator abolished the 01, 044 and 045 dialling prefixes and made every number a flat ten digits [mexico-wiki]. A validator that had encoded the old rule became wrong overnight, and its tests kept passing, because the fixtures encoded the same rule.
Even where a trunk prefix exists, it is not always a single zero. Russia's is 8, a legacy of the Soviet plan [russia-wiki]. Hungary's is the two-digit 06 [hungary-wiki], so a strip-one-zero rule leaves a 6 behind and manufactures a wrong number that is the right length. Brazil's domestic long-distance form is a 0 followed by a two-digit carrier-selection code — three digits that change depending on which carrier the caller chose, so they cannot be matched as a constant at all. Google's libphonenumber team maintains a list of falsehoods programmers believe about phone numbers, and 'a leading zero in numbers formatted for domestic usage can always be discarded when dialing from abroad' is on it, with Italy as the worked example [falsehoods].
| Domestic trunk prefix | Countries (of our 38) | What strip-the-zero does |
|---|---|---|
| 0, dropped in international form | Austria, Australia, Belgium, China (geographic numbers only), Finland, France, Germany, India, Indonesia, Ireland, Israel, Japan, Netherlands, Nigeria, Romania, South Korea, Sweden, Switzerland, Taiwan, Thailand, Turkey, Ukraine, United Kingdom, Vietnam | Correct — 24 countries |
| None — the leading digit belongs to the number | Denmark, Greece, Hong Kong, Italy, Mexico, Norway, Poland, Portugal, Spain | Corrupts the number by deleting a significant digit — 9 countries |
| None — the 1 dialled for long distance is the country code | United States, Canada | Harmless by luck: there is no zero to strip |
| 8 | Russia | Leaves the 8 in place: an 11-digit national number that fits no plan |
| 06 | Hungary | Strips the 0 and keeps the 6: a wrong number of the right length |
| 0 + a two-digit carrier-selection code | Brazil | Leaves the carrier code in: two extra digits that look like an area code |
Length is fixed only in North America
The North American Numbering Plan gives every number exactly ten digits after the country code, which is why so much software written in the United States assumes a phone number has a length. Germany has an open numbering plan: area codes and subscriber numbers both vary in size, and there is no single national length [germany-wiki] — our plan table records German national numbers from 6 to 13 digits, and Austria's from 4 to 13. Hong Kong has no area codes and no trunk prefix; every number is a flat eight digits. Ukraine (+380), Bangladesh (+880) and Hong Kong (+852) have three-digit country codes, which breaks any parser that peels off 'the first one or two digits'. The only length rule E.164 imposes is the 15-digit ceiling, so a 16-digit string is definitively invalid — which is exactly what one of our own bugs produced, as we will get to.
Length is not even stable for a given number. On 6 October 2019 every landline in the Philippines' area code 2 — Metro Manila — grew from seven digits to eight by regulatory order [ph-wiki]. On 22 April 2000 the United Kingdom's Big Number Change replaced London's 0171 and 0181 codes with a single 020 and lengthened the local number to eight digits [bnc-wiki]. Numbers stored in databases did not update themselves on either date, and a validator that rejected the new length because it disagreed with the fixtures would have rejected every resident of the capital. Numbering plans are legislation, and legislation changes; a test suite pinned to today's plan is a snapshot, not a proof.
A country code is not a country
Software loves to derive a country from a dial code, and the derivation is lossy in both directions. Country code 1 is not the United States: the North American Numbering Plan spans twenty-five regions in twenty countries, so a +1 number may be in Jamaica, Bermuda or Guam [nanp-wiki]. Country code 7 is shared by Russia and Kazakhstan [russia-wiki]. Country code 44 covers Guernsey, Jersey and the Isle of Man, none of which is part of the United Kingdom, and 39 serves both Italy and Vatican City [cc-list]. The table of codes also grows: Kosovo's +383 only entered service in 2017 [cc-list], so any hardcoded list of country codes is a list of the countries that existed when the developer last looked. The libphonenumber falsehoods list puts 'each country calling code corresponds to exactly one country' alongside its twin, 'each country has only one country calling code' [falsehoods]. Both are false.
Digits that are not digits, and letters that are
1-800-FLOWERS is a real, dialable phone number. The letters map to digits through the keypad layout standardised in ITU-T E.161 [e161], so alphabetic input is legitimate, and a system that rejects it is rejecting a number the phone network accepts. Extensions are the opposite problem: '415-555-0142 x204' is common in business contact data, E.164 has no slot for the extension, and the tel URI scheme in RFC 3966 gives it a dedicated parameter, ';ext=', precisely so that it is not appended to the digits [rfc3966]. Store the extension in its own field or lose it; never concatenate it, because +14155550142204 is a different, 14-digit, syntactically valid number.
Then there are digits that are not ASCII. In Egypt, phone numbers are commonly written in Arabic-Indic numerals, and Japanese and Chinese input methods produce full-width digits like 123 [falsehoods]. Most normalisation code, including ours, cleans a number by deleting everything that is not a digit — using a regular expression whose idea of a digit is 0 to 9. For generated output that is fine. For a user typing in Cairo it deletes the entire number and reports the field as empty. If your product has international users, normalise Unicode digits before you strip, or at least fail with a message rather than a blank.
The spreadsheet eats the zero
Phone numbers spend a surprising amount of their lives in spreadsheets, and a spreadsheet treats a column of digits as numbers. Leading zeros vanish: the UK drama number 07700 900123 becomes 7700900123, and in Italy — where the zero is significant — the corruption is permanent. Long values are displayed in scientific notation, so +447700900123 typed without the plus is shown as 4.477E+11 and, on export to CSV, may be written that way too. Excel's precision is 15 significant digits, so anything longer is rounded to zeros; Microsoft's own guidance lists phone numbers among the values that must be stored as text [excel]. E.164's 15-digit ceiling fits inside that limit by exactly nothing — one stray digit and the last one becomes a zero. Any pipeline that round-trips contact data through a spreadsheet, and someone's eventually will, needs a fixture with a leading zero to prove it survives.
A phone number is not an identity
The deepest falsehood is the one data models encode: that a phone number identifies one person. Numbers are recycled — the FCC operates a Reassigned Numbers Database because disconnected numbers are reissued and the new holder receives the old holder's messages [rnd]. Number portability means the area code no longer reveals location or carrier, and a number can outlive several moves and several phones [falsehoods]. Households share landlines. One person carries a work number, a personal number and a number from a previous country. Short codes of five or six digits and emergency numbers like 911, 112 and 999 are valid to dial and invalid as contact details. A unique constraint on a phone column, or a login that treats 'same phone' as 'same person', is a bug waiting for the first recycled number.
Numbers that are safe to put in test data
Random digits are the one thing not to use, because most random ten-digit strings belong to somebody, and a staging system that texts its whole contact table is a classic incident. Numbering authorities reserve ranges for exactly this reason: North America's 555-0100 to 555-0199 block with any real area code — the history is in why fake phone numbers start with 555 — and the UK's Ofcom drama ranges such as 020 7946 0xxx and 07700 900xxx [fictitious-wiki]. Sandboxes publish their own: Twilio's test credentials treat +15005550006 as a valid, available number and +15005550001 as an invalid one, deterministically, so an integration test can exercise both branches without a real line [twilio]. Every number in the table below comes from one of those ranges.
A phone-number fixture set worth copying
| Input | What it exercises | Expected behaviour |
|---|---|---|
| +14155550142 | Canonical E.164 in the NANP fiction range | Accepted and stored exactly as given |
| (415) 555-0142 | Domestic formatting with brackets and hyphen | Normalised to +14155550142 |
| 415-555-0142 x204 | An extension | Extension kept in its own field — never appended, never dropped |
| 1-800-FLOWERS | Letters as digits (E.161) | Mapped to +18003569377, or rejected with an explicit message — never stored as letters |
| 06 6982 1234 (Italy) | Leading zero that is part of the number | +390669821234 — not +39669821234 |
| 06 1 234 5678 (Hungary) | Two-digit trunk prefix | +3612345678 |
| 8 495 123 45 67 (Russia) | Trunk prefix that is not zero | +74951234567 |
| 01 55 1234 5678 (Mexico) | A prefix abolished in 2019 | Rejected, or normalised to +525512345678 deliberately — never accepted as a 12-digit number |
| 030 12345678 and 0201 123456 (Germany) | Variable national length | Both valid: +493012345678 and +49201123456 |
| 2123 4567 (Hong Kong) | Eight flat digits, no prefix, three-digit country code | +85221234567 |
| +44 (0) 20 7946 0958 | The bracketed-zero convention | +442079460958 — the (0) is dropped |
| 00442079460958 | The 00 international access prefix | Same number as the row above |
| +4420794609581234 | Sixteen digits | Rejected: over E.164's 15-digit ceiling |
| 911 and 112 | Emergency numbers | Dialable, but rejected as a contact number |
| +81312345678 | Full-width digits | Normalised to +81312345678, or rejected with a message — never silently emptied |
| 07700 900123 (UK drama range) | A leading zero through export and import | Survives a CSV or spreadsheet round trip as text, then normalises to +447700900123 |
| +15005550001 | A provider's documented invalid test number | The sandbox returns 'invalid' every time — the negative branch is testable |
As with the birthday fixtures, run each row through the whole lifecycle rather than the input form alone: store it, display it in national and international format, export it, import it again, and check that the value that comes back is the value that went in. The Italy, Hungary and Russia rows catch a strip-the-zero normaliser; the Germany and Hong Kong rows catch a length check; the full-width and letters rows catch a normaliser that deletes what it does not understand; and the drama-range row catches the spreadsheet somebody added to the pipeline after the tests were written.
How our generator handles phone numbers — including the bugs we shipped
Every profile from our identity generator carries a phone number in the national display format of its country and, alongside it, a phoneE164 field for anyone who needs a canonical value. The first implementation derived that field the way most code does: strip the non-digits, prepend the dial code. Two bugs shipped together. A British number that faker produced as 0865 907 8593 became +4408659078593, with the trunk prefix still inside it. A German number that faker had already produced in international form, +49-9019-07665907, became +4949901907665907 — the country code twice, sixteen digits, a value that breaks E.164's ceiling outright.
The obvious fix — strip a leading zero before prepending — would have been worse than the bug, because it is wrong for eleven of our 38 countries: the nine where the leading digit is significant and the two in the NANP where there is no zero at all, plus Russia's 8, Hungary's 06 and Brazil's variable carrier code. What shipped instead is a per-country plan — the trunk prefix, if any, and the permitted range of national-number lengths — taken from each regulator's numbering plan as filed with the ITU [itu-plans], and a conversion that is candidate-and-validate rather than transform-and-hope. It builds every reading the digits could plausibly have (already international, carrying a trunk prefix, bare, or carrying a zero plus a carrier code), accepts the first that fits the country's documented lengths, and if none fits, omits the field.
That omission rule is what caught the second bug, months later. A Swedish number faker emitted as +46182195 has a six-digit national part, one short of the plan's minimum, so the international reading failed — and the converter fell through to the bare reading and prepended the country code to a string that already contained it, producing +4646182195. Measured across 400 draws per country, the defect touched 36 Swedish numbers and 53 Belgian ones and nothing else. The fix was a rule about ambiguity: an explicit plus sign or a leading 00 is an unambiguous statement that what follows is international, and if that reading does not fit the plan there is no other reading to try. The right description of those numbers is that the locale template produced something Sweden's own plan does not allow, and there is no honest E.164 for it.
Measured today, over 400 generated identities for each of our 38 countries — 15,200 phone numbers — 93.6% carry an E.164 value, 33 countries are at 100%, and zero values exceed 15 digits or repeat a country code. The five that fall short are Italy (36%), Mexico (24%), Finland (26%), Belgium (79%) and Sweden (92%), and in each case the cause is upstream: the locale template emits numbers the national plan does not allow. Italy's template can produce a number carrying +36, which is Hungary's country code; Mexico's produces nine digits where the 2019 plan requires ten. We omit the field rather than launder those into something that looks right, because a consumer can handle an absent value, while a plausible wrong one propagates into fixtures nobody rechecks.
The honest limit is that structural validity is not digit-exactness. A length range cannot catch an off-by-one strip — for Britain, Italy or Germany, removing one digit too many turns a legal ten-digit number into a legal nine-digit one, and Austria's 4-to-13 range makes the bound nearly vacuous. What pins the conversion is a verified national-to-E.164 pair per country, round-tripped by the test suite for more than twenty countries, because a worked example is the only check that catches a real mis-conversion. The industry answer to digit-exactness is libphonenumber, whose metadata models the things our schema cannot, such as Brazil's carrier codes as a pattern. We chose not to use it for two reasons that matter to a fixture generator and to almost nobody else: its metadata changes between releases, so the same seed would silently produce a different number after a dependency bump and break the determinism this site guarantees; and it weighs roughly 500 KB against a 120 KB first-load budget. If your numbers must be dialable, use libphonenumber. Ours are built never to ring.
The conclusion is the one this series keeps arriving at: validate less, test more. Accept the letters, the full-width digits, the Roman zero and the thirteen-digit German number; reject only what no plan on earth allows; and spend the effort you save on fixtures that walk each country's rules and on aggregate checks — how many numbers exceeded 15 digits, how many contain their own country code twice — that catch what per-row validation never will. If you want the earlier fields in this series, start with names and birthdays; for the North American rules in depth, see how US area codes work. If you want a thousand numbers from the fiction ranges to run your own checks on, the phone number generator and the free API both produce them, and the API's phoneE164 field is the output of exactly the code described above.
References & sources
- E.164: The international public telecommunication numbering plan — ITU-T
- E.164 — structure and the 15-digit maximum — Wikipedia
- Falsehoods programmers believe about phone numbers — Google libphonenumber
- Telephone numbers in Italy — the leading zero retained since 1998 — Wikipedia
- Telephone numbers in Mexico — the 3 August 2019 change to ten-digit dialling — Wikipedia
- Telephone numbers in Russia — trunk prefix 8, country code shared with Kazakhstan — Wikipedia
- Telephone numbers in Hungary — the 06 domestic prefix — Wikipedia
- Telephone numbers in Germany — an open numbering plan of variable length — Wikipedia
- Telephone numbers in the Philippines — the 6 October 2019 eight-digit migration — Wikipedia
- Big Number Change — 22 April 2000 — Wikipedia
- North American Numbering Plan — twenty-five regions in twenty countries — Wikipedia
- List of country calling codes — Wikipedia
- E.161: Arrangement of digits, letters and symbols on telephones — ITU-T
- RFC 3966: The tel URI for Telephone Numbers — IETF
- Last digits are changed to zeros when you type long numbers in cells of Excel — Microsoft Learn
- Reassigned Numbers Database — FCC
- Test credentials and magic phone numbers — Twilio
- Fictitious telephone number — reserved ranges by country — Wikipedia
- National numbering plans filed with the ITU (E.164 country filings) — ITU-T
Frequently asked questions
What is E.164 format?+
E.164 is the ITU-T recommendation that defines international telephone numbering. A number in E.164 format is a plus sign, a country code of one to three digits, and the national significant number, with no spaces, brackets, hyphens, trunk prefix or extension, and at most 15 digits in total — for example +442079460958. It is a storage and interchange format, not a display format, and it says nothing about which numbers exist or how a country writes its numbers domestically.
Why do Italian phone numbers keep the leading zero in international format?+
Because in Italy the zero is part of the number, not a trunk prefix. Since 1998 Italian landline numbers have carried their leading zero permanently, so a Rome number written 06 6982 1234 is dialled from abroad as +39 06 6982 1234 — the zero stays. Most other countries drop the zero when converting to international format, which is why a generic 'strip the leading zero' rule silently corrupts every Italian landline. Denmark, Greece, Mexico, Norway, Poland, Portugal and Spain also have no trunk prefix, so their leading digit must be kept too.
How long can a phone number be?+
E.164 caps the complete international number — country code included, plus sign excluded — at 15 digits. There is no international minimum: national significant numbers run from five digits on Saint Helena to thirteen in Germany and Austria, whose open numbering plans have no fixed length at all. A validator that requires exactly ten digits accepts North America and rejects most of the world.
Can I use a phone number as a unique identifier for a person?+
Not safely. Numbers are recycled after disconnection — the FCC operates a Reassigned Numbers Database precisely because reassigned numbers reach the wrong person — households share landlines, one person often has several numbers, and number portability means a number can move between carriers and cities without changing. Treat a phone number as a contact channel that needs re-verification, not as a primary key.
What phone numbers are safe to use in test data?+
Use ranges that a numbering authority has reserved so they can never ring a real line: in North America, 555-0100 through 555-0199 with any real area code; in the UK, Ofcom's drama ranges such as 020 7946 0xxx and 07700 900xxx; and for payment or messaging sandboxes, the provider's documented test numbers, such as Twilio's +15005550006. Never invent digits at random — most random ten-digit strings are somebody's real number.
More on phone numbers and numbering plans
- How to Keep Your Phone Number Private When Signing Up Online
- Why Fake Phone Numbers Start With 555
- US Area Codes Explained: How Numbers Are Assigned
- How Free Address Generators Work: Streets, ZIPs & Limits
- US Address Format Explained: USPS Rules & Examples
- What Is a ZIP Code? Format, Structure & How They Work