Most IBAN checkers answer with a red cross and leave you to find the typo yourself. This one names the failure: which position holds an impossible character, how many characters are missing for the country, and what the check digits should have been. When the checksum fails it computes the corrected IBAN, and when a letter O sits where a digit belongs it says so. All of it runs in your browser; the numbers never leave this tab.

What the validator checks

Every line runs through the same chain, and the report stops at the first link that breaks, because everything after it would be noise:

  1. Character set. An IBAN is A to Z and 0 to 9, nothing else. Spaces, hyphens, quotes and a pasted "IBAN" prefix are stripped first, so what remains flagged was really part of the number. The offending character is named with its position.
  2. Country. The first two letters must be one of the 89 codes in the registry. UK instead of GB is called out explicitly, because it is the one everyone types.
  3. Length. Each country registered exactly one length. The report says "an IBAN from Germany is 22 characters, this one has 21" rather than "wrong length".
  4. Structure. The registry defines the account part per country: a British BBAN is 4 letters and 14 digits, a German one 18 digits. A character of the wrong kind is reported with its position, and if it is one of the classic lookalikes (O for 0, I or L for 1, S for 5, B for 8), the reading that would fix it is suggested.
  5. Checksum. The ISO 7064 mod-97 test. On failure the tool computes the check digits this account number would need and shows the corrected IBAN, then hunts for a single lookalike swap or two adjacent characters that, exchanged, make the number verify.

A valid IBAN gets taken apart instead: country, check digits, bank code, branch and account number, marked in the number itself, for the countries whose layout is public (Germany, Austria, Switzerland, the Netherlands, France, Italy, Spain, Belgium, the United Kingdom, Poland).

Checking a whole list

Paste one IBAN per line and the report switches to a table: one verdict and one reason per line, with the corrected number inline where one could be computed, and a count of checked, valid and invalid at the bottom. That is the mode for the situations where broken IBANs actually surface in bulk: a customer table before a database migration, a CSV export on its way into accounting software, a SEPA batch file that the bank rejected with an unhelpful error code. Surrounding quotes from CSV exports and a stray iban header line are tolerated, so a copied column usually works as pasted.

How an IBAN is put together

Four characters of international header, then the national account number as it existed before the IBAN did:

  • Positions 1 to 2: the ISO 3166 country code. DE, AT, FR, GB.
  • Positions 3 to 4: two check digits computed over everything else. Always numeric, and by construction always in the range 02 to 98, so 00, 01 and 99 are impossible in a real IBAN. Test data generated with placeholder check digits fails exactly here.
  • Position 5 onwards: the BBAN, the domestic format wrapped unchanged. Germany packs an 8-digit bank code (BLZ) and a 10-digit account number into it. The United Kingdom uses a 4-letter bank code, a 6-digit sort code and an 8-digit account number, which is why British IBANs contain letters after the header and German ones never do. France appends a 2-digit national key (clé RIB) of its own, Italy prepends a single check letter (CIN).

ISO 13616 allows up to 34 characters; the registered formats currently run from Norway's 15 to Russia's 33. Because each country's layout is fixed to the character, a validator does not have to guess: a letter in position 7 of a German IBAN is wrong, the same letter in position 7 of a British one is required.

The mod-97 check, step by step

The checksum is one division. Take DE89 3704 0044 0532 0130 00:

  1. Move the first four characters to the end: 370400440532013000DE89.
  2. Replace letters with numbers, A=10 through Z=35. D is 13, E is 14: 370400440532013000131489.
  3. Divide by 97. If the remainder is 1, the IBAN verifies.

That last number has 24 digits, which is where implementations go wrong: it does not fit in a 64-bit float. The trick, sanctioned by ISO 7064 itself, is a running remainder. Take the first 9 digits, divide, carry the remainder in front of the next digits, repeat:

370400440              % 97 = 23
23 · 532013000  →  23532013000 % 97 = 16
16 · 131489     →  16131489    % 97 = 1   ✓

The remainder is 1, so DE89… is a well-formed IBAN. Generation runs the same machinery backwards: write 00 as a placeholder check digit pair, compute the remainder, and the real check digits are 98 minus it. That construction is why the result always lands between 02 and 98, and why a 00, 01 or 99 in positions 3 and 4 proves the number was never issued by a bank.

97 being prime buys a strong guarantee: any single wrong character changes the remainder, and so does any transposition of two characters. Two independent errors in the same number cancel out with a probability of about 1 in 97. For comparison, simple digit-sum schemes miss whole classes of transpositions; mod-97 was chosen for the IBAN precisely because copy errors between paper and keyboard are its whole threat model.

IBAN lengths by country

One registered length per country, from the SWIFT IBAN registry. The structure column reads as the account part after the four header characters: n digits, a letters, c either.

CodeCountryLengthBBAN structure
ADAndorra244n 4n 12c
AEUnited Arab Emirates233n 16n
ALAlbania288n 16c
ATAustria205n 11n
AZAzerbaijan284a 20c
BABosnia and Herzegovina203n 3n 8n 2n
BEBelgium163n 7n 2n
BGBulgaria224a 4n 2n 8c
BHBahrain224a 14c
BIBurundi275n 5n 11n 2n
BRBrazil298n 5n 10n 1a 1c
BYBelarus284c 4n 16c
CHSwitzerland215n 12c
CRCosta Rica224n 14n
CYCyprus283n 5n 16c
CZCzechia244n 6n 10n
DEGermany228n 10n
DJDjibouti275n 5n 11n 2n
DKDenmark184n 9n 1n
DODominican Republic284c 20n
EEEstonia202n 2n 11n 1n
EGEgypt294n 4n 17n
ESSpain244n 4n 1n 1n 10n
FIFinland183n 11n
FKFalkland Islands182a 12n
FOFaroe Islands184n 9n 1n
FRFrance275n 5n 11c 2n
GBUnited Kingdom224a 6n 8n
GEGeorgia222a 16n
GIGibraltar234a 15c
GLGreenland184n 9n 1n
GRGreece273n 4n 16c
GTGuatemala284c 20c
HNHonduras284a 20n
HRCroatia217n 10n
HUHungary283n 4n 1n 15n 1n
IEIreland224a 6n 8n
ILIsrael233n 3n 13n
IQIraq234a 3n 12n
ISIceland264n 2n 6n 10n
ITItaly271a 5n 5n 12c
JOJordan304a 4n 18c
KWKuwait304a 22c
KZKazakhstan203n 13c
LBLebanon284n 20c
LCSaint Lucia324a 24c
LILiechtenstein215n 12c
LTLithuania205n 11n
LULuxembourg203n 13c
LVLatvia214a 13c
LYLibya253n 3n 15n
MCMonaco275n 5n 11c 2n
MDMoldova242c 18c
MEMontenegro223n 13n 2n
MKNorth Macedonia193n 10c 2n
MNMongolia204n 12n
MRMauritania275n 5n 11n 2n
MTMalta314a 5n 18c
MUMauritius304a 2n 2n 12n 3n 3a
NINicaragua284a 20n
NLNetherlands184a 10n
NONorway154n 6n 1n
OMOman233n 16c
PKPakistan244a 16c
PLPoland288n 16n
PSPalestine294a 21c
PTPortugal254n 4n 11n 2n
QAQatar294a 21c
RORomania244a 16c
RSSerbia223n 13n 2n
RURussia339n 5n 15c
SASaudi Arabia242n 18c
SCSeychelles314a 2n 2n 16n 3a
SDSudan182n 12n
SESweden243n 16n 1n
SISlovenia195n 8n 2n
SKSlovakia244n 6n 10n
SMSan Marino271a 5n 5n 12c
SOSomalia234n 3n 12n
STSao Tome and Principe258n 11n 2n
SVEl Salvador284a 20n
TLTimor-Leste233n 14n 2n
TNTunisia242n 3n 13n 2n
TRTurkey265n 1n 16c
UAUkraine296n 19c
VAVatican City223n 15n
VGBritish Virgin Islands244a 16n
XKKosovo204n 10n 2n
YEYemen304a 4n 18c

The countries missing from this table are the point of it: the United States, Canada, Australia, India, China and Japan have not adopted IBAN. A transfer to them uses domestic routing numbers plus a BIC, and a form that demands an IBAN for a US account is asking for something that does not exist.

The mistakes that actually happen

After validating enough imported customer data, the same handful of failure shapes keeps returning:

  • Excel ate the number. A cell formatted as General treats a pasted IBAN-without-letters as a number: leading zeros vanish and anything past 15 significant digits becomes zero, displayed as 2.0891E+21. The re-exported CSV then holds a 20-digit number that never was an IBAN. It surfaces here as a length error or a checksum failure with no visible typo; the fix is to reimport with the column typed as Text, because the lost digits are not recoverable.
  • O for 0, I for 1, S for 5, B for 8. Numbers that arrive over the phone, from a scanned invoice or from OCR carry lookalike letters. In an all-digit BBAN the structure check localises them exactly; in the letters-allowed part of a French or Maltese IBAN only the checksum notices, which is why the tool tries the swap in both cases.
  • The paper format's line break. Printed IBANs come in groups of four. When a copy out of a PDF breaks the line mid-number, two lines arrive, each failing with a length error. Two consecutive too-short lines from the same country in a batch report are that pattern.
  • Swapped neighbours. Typing DE98 for DE89, or exchanging two digits mid-account. The checksum always catches it, and the suggestion engine names the two positions when a single swap explains the failure.
  • Placeholder check digits. Fixtures and dummy records built by hand tend to use 00 or 99 after the country code. Those pairs cannot come out of the mod-97 construction, so they mark data that was never real, which is useful to know before blaming an import script.

If what you need is the opposite, structurally valid test data that is guaranteed to belong to no real account, that is a different job with its own trap doors, and it is what the IBAN generator is for.

Validating an IBAN in code

The whole check is a rearrangement and one modulo, with a single trap: the converted number has up to 36 digits, and a 64-bit float is exact only to 253, roughly 16 digits. Number, parseInt and Excel's MOD all silently return wrong answers on it. In JavaScript, BigInt sidesteps that:

function isValidIban(iban) {
  const s = iban.replace(/\s+/g, '').toUpperCase();
  if (!/^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$/.test(s)) return false;
  const digits = (s.slice(4) + s.slice(0, 4))
    .replace(/[A-Z]/g, (c) => c.charCodeAt(0) - 55);
  return BigInt(digits) % 97n === 1n;
}

Python's integers are arbitrary precision, and int(c, 36) happens to be exactly the letter-to-number mapping the standard wants:

import re

def is_valid_iban(iban: str) -> bool:
    s = "".join(iban.split()).upper()
    if not re.fullmatch(r"[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}", s):
        return False
    digits = "".join(str(int(c, 36)) for c in s[4:] + s[:4])
    return int(digits) % 97 == 1

Where BigInt is off the table (old runtimes, embedded targets), use the running remainder from the walkthrough above: fold the string into an accumulator one digit at a time, taking % 97 as you go, and the intermediate value never exceeds four digits. This page's engine does exactly that. Both snippets check shape and checksum only; per-country length and structure need the registry table, which is what packages like ibantools (JavaScript) and schwifty (Python) maintain so you do not have to.

What a valid IBAN does not prove

Worth being precise about, because payment forms and their error messages blur it:

  • Valid means well-formed. Length, structure and checksum match the registry. That is all any offline validator, this one included, can establish.
  • It does not mean the account exists. The checksum space is vastly larger than the set of issued accounts; a randomly generated IBAN that passes mod-97 almost certainly belongs to nobody. Existence is only known to the account-holding bank.
  • National check digits are a separate layer. The French clé RIB, the Spanish dígitos de control and the Italian CIN live inside the BBAN with their own algorithms. A number can pass mod-97 and still fail those; banks check both.
  • The name is checked too, now. For euro transfers in the EU, Verification of Payee has been mandatory since 9 October 2025: the receiving bank matches the payee name against the account before the money moves. "IBAN correct, name wrong" has become a visible warning instead of a silent misdirection.
  • No BIC is derived here. Mapping an IBAN to its BIC requires a complete, current directory of bank codes; national lists change monthly and no client-side snapshot stays correct. Tools that print one anyway are serving data of unknown age. For SEPA payments the point is moot, since the IBAN alone has been sufficient since 2016.

Validate at the point of entry, store the electronic format (no spaces), and treat a checksum failure as "retype from the source document", never as "fix the character that looks wrong". The check exists so that the typo is caught before the transfer, not after.

When an IBAN is rejected

How does the IBAN mod-97 check work?

Move the first four characters (country code and check digits) to the end, replace every letter with its number (A=10, B=11 … Z=35), and divide the resulting number by 97. The remainder must be 1. The check digits were generated the other way round: put 00 in their place, take 98 minus the remainder, and that pair is what the bank prints. The scheme is ISO 7064 MOD 97-10, and because 97 is prime it detects every single wrong character and every swap of two characters.

How do I validate an IBAN in Excel?

There is no built-in function, and =MOD(A1,97) on the raw number cannot work because Excel keeps only 15 significant digits while a converted IBAN has up to 36. The workable routes are a VBA function that walks the string with a running remainder, or Power Query with the same loop. Before any of that, format the column as Text: a General cell drops leading zeros and turns a pasted IBAN into 2.0891E+21, destroying the digits you wanted to check. A quick sanity check without the checksum is =LEN(SUBSTITUTE(A1," ","")) compared against the expected length for the country.

How do I validate an IBAN in Python?

Strip spaces, uppercase, move the first four characters to the end, convert with int(c, 36) per character (which maps 0-9 to themselves and A-Z to 10-35), join the digits and test int(digits) % 97 == 1. Python integers are arbitrary precision, so the 30-plus digit number is no problem. If you also want length and structure per country, the schwifty package carries the registry and raises a specific exception naming what failed.

How do I validate an IBAN in JavaScript?

The same rearrange-and-divide, but the number does not fit: Number is exact only to 2^53, about 16 digits, and a converted IBAN has up to 36. Use BigInt(digits) % 97n === 1n, or a running remainder that takes the string a few digits at a time so it also works where BigInt is unavailable. The ibantools package on npm does this plus per-country length and structure if you would rather not maintain the registry yourself.

Is there a regex to validate IBANs?

Only for the shape: ^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$ matches anything IBAN-like, and per-country patterns such as ^DE[0-9]{20}$ or ^GB[0-9]{2}[A-Z]{4}[0-9]{14}$ pin the national structure. No regex can compute the mod-97 checksum, so a regex-only validator accepts DE00 followed by any 18 digits. Use the pattern as a cheap pre-filter and follow it with the checksum.

Why does a bank transfer fail even though the IBAN is valid?

Because the checksum only proves the number is well-formed. The account may be closed, the number may never have been issued, or the national check digits inside the account part may fail the bank's own algorithm. Since 9 October 2025, euro transfers in the EU also go through Verification of Payee: the receiving bank compares the name you entered against the account holder and warns or blocks on a mismatch. A syntactically perfect IBAN fails any of these later checks without contradiction.

Why do IBAN lengths differ between countries?

The IBAN did not replace national account formats, it wrapped them. After the four-character header comes the domestic BBAN unchanged: Norway's 11-digit account numbers make a 15-character IBAN, Germany's 8-digit bank code plus 10-digit account make 22, and Malta reaches 31. ISO 13616 caps the total at 34 characters, and each country registered one fixed length, which is why a length check alone already catches a lot of broken data.

Can two accounts have the same IBAN?

No. Within a country the BBAN identifies exactly one account, and the country prefix makes it globally unique. The edge cases run the other way: after bank mergers one account is sometimes reachable under two IBANs for a transition period because the old bank code keeps routing, and a closed account's number can in principle be reissued years later in some countries. Two live accounts sharing one IBAN would break settlement and does not occur.

What does "invalid check digits" mean on a bank form?

The form ran the mod-97 test and the remainder was not 1, so at least one character in what you typed differs from the number the bank issued. It is almost always a typo: a swapped pair of digits, a 0 read as the letter O, or a digit dropped while copying from paper. Retype from the original document rather than fixing single characters by eye, and remember every character counts, including the two digits right after the country code.

Is it safe to paste customer IBANs into an online IBAN checker?

Check where the processing happens first: most online validators send every number to their server, and an IBAN is personal data under the GDPR because it identifies an account holder, which makes that upload something your data-processing agreements have to cover. This validator runs the whole check in your browser: the registry ships with the page, the numbers are never transmitted, and the check works offline once the page is loaded.

Do the IBAN check digits catch every typo?

Every single-character error and every transposition of two characters, yes: with a prime modulus of 97 neither kind of change can leave the remainder at 1. Two independent errors in one number slip through with a probability of about 1 in 97, roughly one percent. That is why banks layer national check digits inside the BBAN on top, and why a checksum pass is a strong hint rather than a guarantee that the number is the one on the invoice.