Skip to main content
CodeLint.Dev Dev Tools
FinTech 9 min read

IBAN Validator: Structure, MOD-97 Checksum Algorithm, and Developer Guide

If you build fintech applications that handle bank transfers across borders, you will encounter IBANs — International Bank Account Numbers. Accepting an invalid IBAN means the payment fails or, worse, is routed to the wrong account. This guide covers the ISO 13616 standard in full: IBAN structure, country-specific formats, the MOD-97 checksum algorithm you can implement in minutes, the relationship between IBANs and SWIFT/BIC codes, and working validation code in Python, JavaScript, and Go.

Try the tool
IBAN Validator
Validate an IBAN free →

What Is an IBAN? The ISO 13616 Standard

The International Bank Account Number (IBAN) is a standardised format for identifying a bank account across borders, defined in ISO 13616-1:2020. It was developed by ECBS (European Committee for Banking Standards) and adopted globally for cross-border SEPA payments in the European Union and beyond.

An IBAN is used to:

  • Identify the specific bank and account for a wire transfer
  • Allow automated validation before a payment is submitted (reducing return fees)
  • Enable straight-through processing (STP) in payment systems without manual intervention

As of 2025, IBANs are used in over 80 countries, including all EU/EEA member states, the UK, Switzerland, Norway, Turkey, Saudi Arabia, the UAE, and most countries in the Middle East and North Africa. The United States, Canada, Australia, India, and China do not use IBAN — they use domestic routing numbers and account numbers instead.

IBAN Structure: Country Code, Check Digits, and BBAN

Every IBAN has the same three-part structure:

GB29 NWBK 6016 1331 9268 19
│─┘ │──┘ │───────────────────┘
CC  CD         BBAN
  • CC (Country Code) — 2-letter ISO 3166-1 alpha-2 code (e.g. GB for the United Kingdom, DE for Germany, FR for France)
  • CD (Check Digits) — 2 numeric digits computed using the MOD-97 algorithm. They protect against transcription errors.
  • BBAN (Basic Bank Account Number) — the domestic bank account identifier. The format and length are defined per-country. The BBAN for the UK (GB) is always 22 characters; for Germany (DE) it is 18 characters; for France (FR) it is 23 characters.

Key per-country lengths (total IBAN length):

Country Code Total length
NorwayNO15
GermanyDE22
United KingdomGB22
FranceFR27
Saudi ArabiaSA24
MaltaMT31 (longest)

The printed format includes spaces every 4 characters for readability (e.g. GB29 NWBK 6016 1331 9268 19). The electronic format has no spaces (e.g. GB29NWBK60161331926819). Always strip spaces before validation or storage.

The MOD-97 Check Digit Algorithm

The two check digits in positions 3–4 of an IBAN are computed using the MOD-97 algorithm (defined in ISO 7064). The validation procedure:

  1. Remove spaces from the IBAN and convert to uppercase.
  2. Rearrange: move the first 4 characters (country code + check digits) to the end. GB29NWBK60161331926819NWBK60161331926819GB29
  3. Replace letters with digits: A=10, B=11, ..., Z=35. N=23, W=32, B=11, K=20, ..., G=16, B=11. The string becomes a long integer.
  4. Compute the remainder: divide the integer by 97. If the remainder is 1, the IBAN is valid. If it is anything else, the IBAN is invalid.
# Quick Python check
int("232011601613319268191611") % 97 == 1  # True → valid

Generating check digits for a new IBAN:

  1. Assemble the IBAN with 00 as placeholder check digits.
  2. Rearrange (move first 4 to the end) and replace letters with digits.
  3. Compute 98 − (integer mod 97). Zero-pad to 2 digits — this is the check digit pair.

This algorithm catches all single-digit errors and all single-transposition errors (two adjacent digits swapped), which together cover the vast majority of manual entry mistakes.

SWIFT/BIC Codes: The Companion Identifier

An IBAN identifies the account. A SWIFT/BIC code identifies the bank. For international wire transfers, you typically need both.

A BIC (Bank Identifier Code, also called a SWIFT code) is defined in ISO 9362 and has the structure:

NWBK GB 2L XXX
│──┘ │─┘ │─┘ │─┘
Bank CC  Loc  Branch (optional)
  • Bank code: 4 letters identifying the institution (e.g. NWBK = NatWest)
  • Country code: 2-letter ISO country code (GB)
  • Location code: 2 characters (letters or digits)
  • Branch code: optional 3 characters; XXX means head office

BICs are either 8 or 11 characters. An 8-character BIC is equivalent to the 11-character version with XXX suffix.

In the SEPA zone (EU + EEA countries), the IBAN alone is sufficient for euro transfers — you do not need a BIC for domestic SEPA payments. For non-SEPA international wires, both IBAN and BIC are required.

IBAN Validation in Python, JavaScript, and Go

These are production-ready implementations of the MOD-97 algorithm.

Python Python
# IBAN country lengths (subset — full list has 80+ entries)
IBAN_LENGTHS = {
    'AL': 28, 'AT': 20, 'BE': 16, 'CH': 21, 'CY': 28,
    'DE': 22, 'DK': 18, 'ES': 24, 'FI': 18, 'FR': 27,
    'GB': 22, 'GR': 27, 'HR': 21, 'HU': 28, 'IE': 22,
    'IT': 27, 'LU': 20, 'MT': 31, 'NL': 18, 'NO': 15,
    'PL': 28, 'PT': 25, 'RO': 24, 'SA': 24, 'SE': 24,
    'TR': 26, 'AE': 23,
}

def validate_iban(iban: str) -> tuple[bool, str]:
    """Returns (is_valid, error_message)."""
    cleaned = iban.replace(' ', '').upper()

    if len(cleaned) < 4:
        return False, "Too short"

    country = cleaned[:2]
    if not country.isalpha():
        return False, "Country code must be 2 letters"

    expected_len = IBAN_LENGTHS.get(country)
    if expected_len is None:
        return False, f"Unknown country code: {country}"
    if len(cleaned) != expected_len:
        return False, f"Wrong length for {country}: expected {expected_len}, got {len(cleaned)}"

    # Rearrange: move first 4 chars to end
    rearranged = cleaned[4:] + cleaned[:4]

    # Replace letters with digits (A=10, B=11, ..., Z=35)
    numeric = ''.join(
        str(ord(c) - ord('A') + 10) if c.isalpha() else c
        for c in rearranged
    )

    if int(numeric) % 97 == 1:
        return True, "Valid IBAN"
    return False, "Invalid check digits (MOD-97 failed)"

# Test
print(validate_iban("GB29 NWBK 6016 1331 9268 19"))  # (True, 'Valid IBAN')
print(validate_iban("GB29 NWBK 6016 1331 9268 18"))  # (False, 'Invalid check digits...')
JavaScript JavaScript / TypeScript
const IBAN_LENGTHS: Record<string, number> = {
  AL: 28, AT: 20, BE: 16, CH: 21, CY: 28, DE: 22, DK: 18,
  ES: 24, FI: 18, FR: 27, GB: 22, GR: 27, HR: 21, HU: 28,
  IE: 22, IT: 27, LU: 20, MT: 31, NL: 18, NO: 15, PL: 28,
  PT: 25, RO: 24, SA: 24, SE: 24, TR: 26, AE: 23,
};

function validateIBAN(iban: string): { valid: boolean; error?: string } {
  const cleaned = iban.replace(/s/g, '').toUpperCase();
  if (cleaned.length < 4) return { valid: false, error: 'Too short' };

  const country = cleaned.slice(0, 2);
  if (!/^[A-Z]{2}$/.test(country)) return { valid: false, error: 'Invalid country code' };

  const expectedLen = IBAN_LENGTHS[country];
  if (!expectedLen) return { valid: false, error: `Unknown country: ${country}` };
  if (cleaned.length !== expectedLen)
    return { valid: false, error: `Wrong length for ${country}: expected ${expectedLen}` };

  // Rearrange and convert letters to digits
  const rearranged = cleaned.slice(4) + cleaned.slice(0, 4);
  const numeric = rearranged
    .split('')
    .map(c => (c >= 'A' && c <= 'Z' ? String(c.charCodeAt(0) - 55) : c))
    .join('');

  // MOD-97 on large integer (chunk to avoid precision loss)
  let remainder = 0;
  for (let i = 0; i < numeric.length; i += 7) {
    const chunk = remainder + numeric.slice(i, i + 7);
    remainder = parseInt(chunk, 10) % 97;
  }
  return remainder === 1 ? { valid: true } : { valid: false, error: 'MOD-97 check failed' };
}

console.log(validateIBAN('GB29 NWBK 6016 1331 9268 19')); // { valid: true }

Common IBAN Mistakes Developers Make

  1. Not stripping spaces before validation. IBANs are displayed with spaces every 4 characters (the "print format") but processed without spaces (the "electronic format"). Always call .replace(/s/g, '') before running any validation logic or storing to a database.
  2. Storing with spaces. Store IBANs in the electronic format (no spaces). Display them with spaces for readability. Never store the display format — it creates inconsistent matching when you later query by IBAN.
  3. Only checking length, skipping MOD-97. A length check eliminates the obviously wrong but accepts plausible-looking invalid IBANs. The MOD-97 check is fast and catches all single-character errors. Always implement both.
  4. Assuming IBAN means SEPA. Countries outside the EU use IBANs (UAE, Saudi Arabia, Turkey), but they are not on the SEPA network. SEPA transfers only work within the SEPA zone. For IBAN-capable but non-SEPA countries, you still need a SWIFT wire.
  5. Hardcoding country lengths. New countries join the IBAN standard periodically. Hardcoding a fixed set of country lengths means your validator will incorrectly reject valid IBANs from newly joined countries. Use an updated reference dataset or an actively maintained library.

Frequently Asked Questions

What is an IBAN and what is it used for?
An IBAN (International Bank Account Number) is a standardised format defined in ISO 13616 for identifying a specific bank account internationally. It consists of a 2-letter country code, 2 check digits, and a country-specific BBAN (Basic Bank Account Number). IBANs are used for cross-border bank transfers, particularly SEPA payments in Europe. They allow payment systems to validate the account number before transmission, reducing failed and returned payments.
How many digits is an IBAN?
IBAN length varies by country, from 15 characters (Norway) to 31 characters (Malta). Within a given country, the length is fixed — every German IBAN is exactly 22 characters, every UK IBAN is exactly 22 characters, every French IBAN is exactly 27 characters. A length check against the country-specific expected length is the first step in IBAN validation.
How does IBAN validation work (MOD-97 algorithm)?
IBAN validation uses the MOD-97 algorithm from ISO 7064: (1) strip spaces and convert to uppercase; (2) move the first 4 characters to the end; (3) replace each letter with its numeric equivalent (A=10, B=11, ..., Z=35); (4) interpret the result as a large integer and divide by 97. If the remainder is exactly 1, the IBAN is valid. This algorithm detects all single-digit errors and all adjacent-digit transpositions.
What is the difference between an IBAN and a SWIFT/BIC code?
An IBAN identifies a specific bank account. A SWIFT/BIC code identifies a specific bank (or branch). For international wire transfers, you typically provide both: the IBAN so the payment network knows which account to credit, and the BIC so the routing system knows which bank to contact. Within the SEPA zone (EU + EEA), the IBAN alone is sufficient for euro transfers — the BIC is no longer required.
Do the US, India, Canada, and Australia use IBAN?
No. The United States uses routing numbers (ABA numbers) and account numbers. India uses IFSC codes with account numbers. Canada uses transit numbers and account numbers. Australia uses BSB codes and account numbers. IBAN is primarily used in Europe, the Middle East (UAE, Saudi Arabia, Jordan, Lebanon), North Africa, and a handful of Caribbean nations. When sending money to a US or Indian account, you will need a SWIFT/BIC code and the domestic routing identifier instead of an IBAN.
Can the same account have both an IBAN and a domestic account number?
Yes — in IBAN-using countries, the IBAN and the domestic account number refer to the same account. The BBAN portion of the IBAN typically encodes the bank sort code and account number in the country's domestic format. For example, the UK IBAN GB29NWBK60161331926819 encodes sort code 601613 and account number 31926819 within its BBAN. Banks in IBAN countries display both formats; either can be used, depending on whether the transfer is domestic or international.

Ready to try IBAN Validator?

Free, private, and runs entirely in your browser — no sign-up, no server, no data sent anywhere.

Open IBAN Validator