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.
GBfor the United Kingdom,DEfor Germany,FRfor 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 |
|---|---|---|
| Norway | NO | 15 |
| Germany | DE | 22 |
| United Kingdom | GB | 22 |
| France | FR | 27 |
| Saudi Arabia | SA | 24 |
| Malta | MT | 31 (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:
- Remove spaces from the IBAN and convert to uppercase.
- Rearrange: move the first 4 characters (country code + check digits) to the end.
GB29NWBK60161331926819→NWBK60161331926819GB29 - 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. - 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:
- Assemble the IBAN with
00as placeholder check digits. - Rearrange (move first 4 to the end) and replace letters with digits.
- 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;
XXXmeans 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.
# 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...')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
- 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. - 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.
- 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.
- 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.
- 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.