An IBAN is one of the few identifiers whose internal grammar is published in full. Read it from the left and you pass through three zones: a country code, a pair of check digits, and then the national account part. The first two zones are the same everywhere, while the third changes shape from one country to the next.
That mixture is what makes the identifier both easy to parse and easy to mis-validate. This article separates the parts, explains the mod-97 arithmetic that holds them together, and shows why the arithmetic is the least interesting thing the string has to tell you.
What are the parts of an IBAN?
The name stands for international bank account number, and the design goal was to let one string carry enough information for a cross-border transfer to be routed without a separate instruction sheet.
| Position | Part | Who defines it |
|---|---|---|
| First two characters | Country code | An international standard list of two-letter codes |
| Next two characters | Check digits | The IBAN standard itself, identical in every country |
| Everything after that | National account part, often called the BBAN | The national authority of the country concerned |
The country code says which national rules apply; the two digits say whether the string survived transmission intact; the remainder is the account as the country itself writes it. Only the middle zone is standardised by the IBAN rules, and only the middle zone can be verified without knowing the country’s layout.
The examples used throughout this article are deliberately synthetic placeholders, chosen to show how the parts fit together. None of them is a real account identifier, and nothing here should be read as a claim that any particular account exists.
Country code and the two ISO check digits
The two digits after the country code come from an internationally agreed check-character system, the MOD 97-10 arrangement that belongs to the family surveyed in check digit algorithms. Because the arithmetic folds the whole string into those two positions, changing any other character invalidates them.
That property is why the pair is worth computing even when the country layout is unknown. A routine can verify the first four characters against the rest of the string and reach a conclusion about transmission integrity without owning a single national specification. What it cannot do is decide whether the length is right for the country named, because that length lives in the national rules.
Within those national rules the fields differ widely. One country may pack a bank code and an account number into a fixed number of positions; another may add a branch, a currency marker or a national check digit. The IBAN standard records how long each country’s whole string is, but the meaning of each inner field stays with the country that defined it.
Why does the BBAN often carry a national check digit too?
Because the account part was usually a working national number long before it was embedded in a cross-border format. Banks had already built their own guard against transcription errors, and the international wrapper did not remove it. The result is a string carrying two independent checks: the international pair at the front and whatever the country put inside.
The two are computed from different material, so they fail for different reasons. A corrupted country code breaks the international pair; a mistyped account digit inside the national part may break either the national check, the international pair, or both, depending on where it sits. When a validator reports that the format is correct but the check failed, the usual explanation is that the national part and its check no longer agree.
For an implementer this means the same string can be right at one level and wrong at another, and a message that collapses the two is not good enough. Say which check failed, and say which part of the string it covers — the position of the problem is the information the user needs.
Turning letters into numbers: the idea behind mod-97
The arithmetic needs digits, but the string opens with letters. The bridge is a substitution: each letter is replaced by a number according to a fixed published rule, after the first four characters have been moved to the end of the string. The whole transformed value is then divided by ninety-seven, and a valid identifier is one that leaves a specific remainder.
Rearranging rather than appending is the subtle part. Moving the country code and check digits to the back means the digits being verified sit at the end of the number being divided, so they influence the remainder directly. That is what lets the two leading digits act as a check on everything after them.
Two consequences follow for anyone writing code. First, the transformed value is far longer than any ordinary integer type can hold, so the division has to be performed digit by digit or with a big-number facility; attempts to do it with a plain 64-bit integer overflow silently and produce nonsense on some inputs. Second, letters must be converted with the exact published mapping — approximations such as an alphabetic rank starting from one give a remainder that looks plausible and is simply wrong.
Why does a passed check not prove an account exists?
The mod-97 arithmetic compares the string with itself. It has no knowledge of any bank, any customer, or any account, and nobody at an issuing institution is consulted when it runs. A value can satisfy the arithmetic perfectly and still belong to no account at all, because satisfying the arithmetic is exactly what any generator does by design.
Verifying that an account is open and reachable is a different operation with a different cost profile. It needs a lookup against a live directory, which may be rate-limited, may be unavailable outside business hours, and may need a contractual relationship to call at all. Those are the concerns of a boundary between client and server, not of a checksum routine.
The honest error message therefore describes the string. It may say that the format matched and the check digits held, or that the length does not fit the country code given. It must not say that the account exists, and it must not imply that a failed check means the account is closed or fraudulent.
For developers: routing by country and length
Implementation is mostly bookkeeping, and the bookkeeping is where the bugs live.
- Normalise the input first: strip spaces and hyphens and fold letters to one case.
- Read the country code and look it up. If it is unknown, report an unknown scheme rather than a failed check.
- Compare the total length with the length the country defines, and stop there if it does not match — the arithmetic is not meaningful on a string of the wrong size.
- Run the mod-97 transformation and compare the remainder, using a digit-by-digit or big-number routine.
- Verify any national check digit inside the account part with its own rule.
Keep the country table as data, since lengths and layouts change, and keep the two verdicts separate in the return value: an unsupported country is not an invalid account. Where a user pastes a value that came from an untrusted source, remember that normalisation removes presentation and nothing else; it is not a repair step.
Next steps
Take a country your product supports, write down what its national layout contains, and re-read the string with that in mind. Then check a value end to end in the number validation tool, and read numbers without check digits for the opposite case, where a format is the most a validator can ever confirm.