How IBAN Validation Works — MOD-97 Algorithm Explained
An IBAN (International Bank Account Number) is not just a random string of letters and numbers. Every valid IBAN contains a two-digit check code that is mathematically derived from the rest of the number using the MOD-97 algorithm. This guide explains exactly how validation works, how check digits are calculated, and provides working code in JavaScript and Python.
What Makes an IBAN Valid
A valid IBAN has four components:
- Country code — Two uppercase letters identifying the country (e.g., DE for Germany, GB for United Kingdom, FR for France)
- Check digits — Two numeric digits calculated using the MOD-97 algorithm
- BBAN (Basic Bank Account Number) — The country-specific account number, which includes bank code, branch code, and account number. The length varies by country.
For example, a German IBAN looks like this:
| Component | Value | Description |
|---|---|---|
| Country code | DE | Germany |
| Check digits | 89 | MOD-97 check digits |
| Bank code | 3704 | Bundesbank routing number |
| Account number | 0044 0532 0130 00 | 10-digit account, zero-padded |
Full IBAN: DE89 3704 0044 0532 0130 00 (22 characters for Germany)
Each country defines its own IBAN length. Germany uses 22 characters, the UK uses 22, France uses 27, and Norway uses only 15. The IBAN registry maintained by SWIFT lists the exact format for every participating country.
The ISO 7064 MOD-97 Standard
IBAN validation uses the ISO 7064 MOD-97-10 check character system. The number 97 was chosen because it is the largest prime number below 100, which gives the algorithm excellent error-detection properties. It catches:
- All single-character errors (a digit or letter is wrong)
- All transpositions of two adjacent characters
- Over 99% of all other errors (the probability of an undetected error is approximately 1/97)
Step-by-Step Validation Walkthrough
Let us validate the IBAN GB29 NWBK 6016 1331 9268 19 (a UK-format IBAN).
Step 1: Remove spaces and verify length
Strip all spaces: GB29NWBK60161331926819 (22 characters — correct for UK)
Step 2: Move the first four characters to the end
Take the country code and check digits (GB29) and move them to the end:
NWBK60161331926819GB29
Step 3: Convert letters to numbers
Replace each letter with its position in the alphabet plus 9 (A=10, B=11, C=12, ... Z=35):
| Letter | N | W | B | K | G | B |
|---|---|---|---|---|---|---|
| Number | 23 | 32 | 11 | 20 | 16 | 11 |
The rearranged string NWBK60161331926819GB29 becomes:
2332112060161331926819161129
Step 4: Compute MOD 97
Divide this large number by 97 and find the remainder:
2332112060161331926819161129 mod 97 = 1
Step 5: Check the remainder
If the remainder is exactly 1, the IBAN is valid. Any other remainder means the IBAN contains an error.
Why 1 and not 0? The check digits are calculated so the remainder equals 1 (not 0) because this ensures the check digits are never "00", which could be confused with a missing or default check. The formula 98 - remainder used during generation guarantees the result is between 02 and 98.
How Check Digits Are Calculated
When generating an IBAN, the check digits do not exist yet. Here is how to compute them:
- Start with the BBAN — e.g.,
NWBK60161331926819for the UK example - Append the country code + "00" —
NWBK60161331926819GB00(using 00 as placeholder check digits) - Convert letters to numbers —
2332112060161331926819161100 - Compute MOD 97 —
2332112060161331926819161100 mod 97 = 71 - Subtract from 98 —
98 - 71 = 27 - Zero-pad to 2 digits — The check digits are
29(if the result were 5, it would be05)
Result: GB29NWBK60161331926819
Handling Large Numbers in Code
The numeric string produced during IBAN validation can be 30+ digits long. Standard 64-bit floating-point numbers (JavaScript's Number, Python's float) lose precision beyond about 15-16 digits. There are two approaches to handle this:
Approach 1: BigInt (Recommended)
JavaScript's BigInt and Python's native int handle arbitrarily large integers exactly. This is the cleanest approach.
Approach 2: Piecewise Modulo
Process the number in chunks of 9 digits at a time, carrying the remainder forward. This works in any language without big integer support:
- Take the first 9 digits, compute
mod 97 - Prepend the remainder to the next chunk of 7 digits (so you have at most 9 digits again)
- Repeat until all digits are processed
- The final remainder is the result
JavaScript Implementation
/**
* Validate an IBAN using the MOD-97 algorithm.
* @param {string} iban - The IBAN to validate (with or without spaces).
* @returns {boolean} True if the IBAN is valid.
*/
function validateIBAN(iban) {
// Remove spaces and convert to uppercase
const cleaned = iban.replace(/\s/g, '').toUpperCase();
// Basic format check
if (!/^[A-Z]{2}\d{2}[A-Z0-9]{4,30}$/.test(cleaned)) return false;
// Move first 4 chars to end
const rearranged = cleaned.slice(4) + cleaned.slice(0, 4);
// Convert letters to numbers (A=10, B=11, ..., Z=35)
let numericStr = '';
for (const ch of rearranged) {
if (ch >= 'A' && ch <= 'Z') {
numericStr += (ch.charCodeAt(0) - 55); // A=65, 65-55=10
} else {
numericStr += ch;
}
}
// MOD 97 using BigInt
return BigInt(numericStr) % 97n === 1n;
}
// Usage
console.log(validateIBAN('GB29 NWBK 6016 1331 9268 19')); // true
console.log(validateIBAN('DE89 3704 0044 0532 0130 00')); // true
console.log(validateIBAN('GB29 NWBK 6016 1331 9268 18')); // false (last digit wrong)
Without BigInt (Piecewise Method)
/**
* MOD-97 using piecewise arithmetic (no BigInt needed).
* Compatible with older JavaScript environments.
*/
function mod97(numericStr) {
let remainder = 0;
for (let i = 0; i < numericStr.length; i += 7) {
const chunk = String(remainder) + numericStr.substring(i, i + 7);
remainder = parseInt(chunk, 10) % 97;
}
return remainder;
}
function validateIBANCompat(iban) {
const cleaned = iban.replace(/\s/g, '').toUpperCase();
if (!/^[A-Z]{2}\d{2}[A-Z0-9]{4,30}$/.test(cleaned)) return false;
const rearranged = cleaned.slice(4) + cleaned.slice(0, 4);
let numericStr = '';
for (const ch of rearranged) {
numericStr += (ch >= 'A' && ch <= 'Z') ? (ch.charCodeAt(0) - 55) : ch;
}
return mod97(numericStr) === 1;
}
Python Implementation
def validate_iban(iban: str) -> bool:
"""Validate an IBAN using the MOD-97 algorithm."""
# Remove spaces, uppercase
cleaned = iban.replace(' ', '').upper()
# Basic format check
if len(cleaned) < 5 or not cleaned[:2].isalpha() or not cleaned[2:4].isdigit():
return False
# Move first 4 chars to end
rearranged = cleaned[4:] + cleaned[:4]
# Convert letters to numbers
numeric_str = ''
for ch in rearranged:
if ch.isalpha():
numeric_str += str(ord(ch) - 55) # A=10, B=11, etc.
else:
numeric_str += ch
# Python handles big integers natively — no BigInt needed
return int(numeric_str) % 97 == 1
def calculate_check_digits(country_code: str, bban: str) -> str:
"""Calculate IBAN check digits for a country code and BBAN."""
# Append country code + "00" to BBAN
raw = bban + country_code.upper() + "00"
# Convert letters to numbers
numeric_str = ''
for ch in raw:
if ch.isalpha():
numeric_str += str(ord(ch) - 55)
else:
numeric_str += ch
# Check digits = 98 - (numeric mod 97)
check = 98 - (int(numeric_str) % 97)
return str(check).zfill(2)
# Usage
print(validate_iban("GB29 NWBK 6016 1331 9268 19")) # True
print(validate_iban("DE89 3704 0044 0532 0130 00")) # True
print(validate_iban("GB29 NWBK 6016 1331 9268 18")) # False
# Generate check digits
print(calculate_check_digits("GB", "NWBK60161331926819")) # "29"
print(calculate_check_digits("DE", "370400440532013000")) # "89"
Python advantage: Python's int type handles arbitrary-precision integers natively. You do not need any special library or chunking technique — just convert to int and use the % operator directly. This is one of the rare cases where Python's "batteries included" philosophy genuinely simplifies things.
Common Validation Errors
When implementing IBAN validation, watch out for these common mistakes:
- Not removing spaces — IBANs are typically displayed with spaces for readability (e.g., "GB29 NWBK 6016 1331 9268 19") but must be processed without them.
- Case sensitivity — Always convert to uppercase before processing. Lowercase letters produce wrong numeric values.
- Floating-point arithmetic — Using JavaScript's
Numbertype instead ofBigIntfor MOD-97 will give wrong results on IBANs longer than about 15 characters. This is the most common bug in IBAN validation implementations. - Wrong rearrangement — Move exactly the first 4 characters (country code + check digits) to the end. A common mistake is moving only the country code (2 characters).
- Not validating country-specific length — Each country has a fixed IBAN length. A 22-character "FR" IBAN is invalid because French IBANs must be 27 characters.
Try It Yourself
Generate MOD-97-valid IBANs for 30+ countries. Every generated IBAN passes the validation algorithm described above. They use real bank code prefixes but are not connected to any real bank account.
Frequently Asked Questions
MOD-97 refers to the modulo 97 operation used in IBAN validation, defined by ISO 7064. The IBAN is rearranged and converted to a large integer, then divided by 97. If the remainder is exactly 1, the IBAN is valid. This catches over 99% of transcription errors — the probability of an error going undetected is roughly 1 in 97.
To calculate check digits: take the BBAN (country-specific account number), append the country code letters converted to numbers (A=10, B=11, etc.) followed by '00', compute the remainder when divided by 97, then subtract from 98. The result (zero-padded to 2 digits) becomes the check digits placed after the country code.
IBANs can be up to 34 characters long, producing numeric strings with 30+ digits. JavaScript's standard Number type loses precision beyond 15-16 digits (Number.MAX_SAFE_INTEGER is about 9 quadrillion). BigInt handles arbitrarily large integers exactly, which is required for correct MOD-97 computation. Alternatively, you can process the number in chunks using modular arithmetic.