ISIN Check Digit Validation: How the Luhn Algorithm Works
Last updated: February 2026 · 9 min read
Every ISIN ends with a single check digit calculated using the Luhn algorithm. This digit catches transcription errors—swapped digits, single-character typos—with over 90% accuracy. This guide walks through the exact calculation with real ISINs, covers common implementation pitfalls, and provides working pseudocode.
What the Check Digit Catches
The Luhn algorithm detects:
- Any single-digit error (replacing one digit with another)
- Most adjacent transposition errors (swapping two consecutive digits)
- Most twin errors (e.g., 11 → 22)
It does not catch all transposition errors (e.g., swapping 0↔9) or more complex multi-digit errors. It's a quick sanity check, not a cryptographic guarantee.
Step-by-Step Calculation
Let's validate the ISIN US0378331005 (Apple Inc.) step by step.
Step 1: Convert Letters to Numbers
Replace each letter with its numeric value: A=10, B=11, ..., Z=35. Digits remain unchanged.
ISIN: U S 0 3 7 8 3 3 1 0 0 5 Values: 30 28 0 3 7 8 3 3 1 0 0 5 Expanded: 3 0 2 8 0 3 7 8 3 3 1 0 0 5
Step 2: Apply Luhn from the Right
Starting from the rightmost digit, double every second digit. If the result is > 9, subtract 9:
Position (R→L): 14 13 12 11 10 9 8 7 6 5 4 3 2 1
Digits: 3 0 2 8 0 3 7 8 3 3 1 0 0 5
Double even pos: 6 0 4 8 0 3 14 8 6 3 2 0 0 5
Subtract 9 if>9: 6 0 4 8 0 3 5 8 6 3 2 0 0 5
(14-9=5)Step 3: Sum All Digits
Sum = 6+0+4+8+0+3+5+8+6+3+2+0+0+5 = 50 50 mod 10 = 0 ✓ (valid ISIN)
For validation: if the sum mod 10 equals 0, the ISIN is valid. For calculation: the check digit is (10 - (sum_without_check mod 10)) mod 10.
More Examples
| ISIN | Company | Expanded Digits | Sum | Valid? |
|---|---|---|---|---|
| US0378331005 | Apple Inc. | 3 0 2 8 0 3 7 8 3 3 1 0 0 5 | 50 | ✅ |
| DE0007664039 | Volkswagen | 1 3 1 4 0 0 0 7 6 6 4 0 3 9 | 60 | ✅ |
| GB0005405286 | HSBC | 1 6 1 1 0 0 0 5 4 0 5 2 8 6 | 50 | ✅ |
| US0378331006 | (invalid) | 3 0 2 8 0 3 7 8 3 3 1 0 0 6 | 51 | ❌ |
Implementation in Code
JavaScript
function validateISIN(isin) {
if (!/^[A-Z]{2}[A-Z0-9]{9}[0-9]$/.test(isin)) return false;
// Convert to digit string
let digits = '';
for (const ch of isin) {
if (ch >= 'A' && ch <= 'Z') {
digits += (ch.charCodeAt(0) - 55).toString(); // A=10, B=11...
} else {
digits += ch;
}
}
// Luhn from right
let sum = 0;
let alt = false;
for (let i = digits.length - 1; i >= 0; i--) {
let n = parseInt(digits[i]);
if (alt) {
n *= 2;
if (n > 9) n -= 9;
}
sum += n;
alt = !alt;
}
return sum % 10 === 0;
}
// Test
console.log(validateISIN('US0378331005')); // true
console.log(validateISIN('US0378331006')); // falsePython
def validate_isin(isin: str) -> bool:
if len(isin) != 12:
return False
# Convert letters to numbers
digits = ''
for ch in isin:
if ch.isalpha():
digits += str(ord(ch) - ord('A') + 10)
else:
digits += ch
# Luhn algorithm
total = 0
for i, d in enumerate(reversed(digits)):
n = int(d)
if i % 2 == 1: # double every second from right
n *= 2
if n > 9:
n -= 9
total += n
return total % 10 == 0
# Test
assert validate_isin('US0378331005') == True
assert validate_isin('DE0007664039') == True
assert validate_isin('US0378331006') == FalseCommon Implementation Bugs
⚠️ Bug #1: Wrong doubling direction. The Luhn algorithm doubles from the right, not the left. Because letter-to-number conversion produces variable-length digit strings (A→10 is 2 digits, 5→5 is 1 digit), the total length varies. You must process from right to left.
⚠️ Bug #2: Forgetting letter conversion. ISINs contain letters (country code + possibly in the national ID). If you skip the letter→number conversion, the algorithm will produce wrong results.
⚠️ Bug #3: Off-by-one in calculation vs validation. For validation, include the check digit in the sum and verify sum % 10 == 0. For calculation, exclude the check digit, compute the sum, then check digit = (10 - sum % 10) % 10. Mixing these up is a common source of errors.
⚠️ Bug #4: Using the same code for CUSIP check digits. CUSIP uses a different variation of Luhn with different position weighting. Don't reuse ISIN validation for CUSIP or vice versa.
Check Digit Algorithms Compared
| Identifier | Algorithm | Letter Conversion | Direction |
|---|---|---|---|
| ISIN | Luhn (standard) | A=10, B=11, ..., Z=35 | Right to left |
| CUSIP | Modified Luhn | A=10, B=11, ..., Z=35, *=36, @=37, #=38 | Right to left, different weighting |
| SEDOL | Weighted sum mod 10 | B=11, C=12, ..., Z=35 (no vowels) | Left to right, weights: 1,3,1,7,3,9 |
| WKN | None | N/A | N/A |
| VALOR | None | N/A | N/A |
Frequently Asked Questions
Can two different ISINs have the same check digit?
Yes, absolutely. The check digit is a single digit (0-9), so roughly 10% of all ISINs share any given check digit. The check digit only validates that specific ISIN—it's not a unique identifier on its own.
What happens if I compute the check digit and it doesn't match?
The ISIN is either mistyped or corrupted. Check for common errors: swapped digits, wrong character, extra/missing character. If the country code is wrong, the entire ISIN will be invalid.
Is there a standard library for ISIN validation?
Most languages have packages: Python has python-stdnum (stdnum.isin), JavaScript has isin-validator on npm, Java has Apache Commons Validator. For one-off checks, our converter tool validates ISINs automatically.
Validate & Convert ISINs
Our tool validates the check digit automatically and converts between ISIN, CUSIP, WKN, SEDOL, and more.
Open Converter →