Luhn Algorithm (Mod 10) — Developer Guide
The Luhn algorithm (also called Mod 10) is a simple checksum formula used to validate identification numbers. Created by IBM scientist Hans Peter Luhn in 1954, it's the standard check digit mechanism for credit card numbers, IMEI numbers, ICCID/SIM numbers, and many other identification systems. This guide covers everything a developer needs: how it works, step-by-step examples, implementations in 4 languages, and common pitfalls.
How Luhn Works
The algorithm processes digits from right to left:
- Starting from the rightmost digit (the check digit), moving left
- Double every second digit (i.e., every digit in an even position from the right, starting with the second-to-last)
- If the doubled value is > 9, subtract 9 (equivalently, add the two digits of the product: e.g., 14 → 1+4 = 5, which is 14 − 9)
- Sum all digits (the unmodified ones + the processed ones)
- If the total mod 10 = 0, the number is valid
Step-by-Step Example
Let's validate the credit card number 4532 0123 4567 8917:
| Position (from right) | Digit | Action | Result |
|---|---|---|---|
| 16 | 4 | Double: 4 × 2 = 8 | 8 |
| 15 | 5 | Keep as-is | 5 |
| 14 | 3 | Double: 3 × 2 = 6 | 6 |
| 13 | 2 | Keep as-is | 2 |
| 12 | 0 | Double: 0 × 2 = 0 | 0 |
| 11 | 1 | Keep as-is | 1 |
| 10 | 2 | Double: 2 × 2 = 4 | 4 |
| 9 | 3 | Keep as-is | 3 |
| 8 | 4 | Double: 4 × 2 = 8 | 8 |
| 7 | 5 | Keep as-is | 5 |
| 6 | 6 | Double: 6 × 2 = 12 → 12 − 9 = 3 | 3 |
| 5 | 7 | Keep as-is | 7 |
| 4 | 8 | Double: 8 × 2 = 16 → 16 − 9 = 7 | 7 |
| 3 | 9 | Keep as-is | 9 |
| 2 | 1 | Double: 1 × 2 = 2 | 2 |
| 1 (check) | 7 | Keep as-is (check digit) | 7 |
Sum = 8+5+6+2+0+1+4+3+8+5+3+7+7+9+2+7 = 70. 70 mod 10 = 0 → Valid.
Generating a Check Digit
To generate a check digit for a number without one:
- Append
0as a placeholder check digit - Compute the Luhn sum
- If sum mod 10 = r, the check digit =
(10 − r) mod 10
Example
Generate a check digit for 4532 0123 4567 891:
- Append 0:
4532 0123 4567 8910 - Luhn sum = 63
- Check digit = (10 − 3) mod 10 = 7
- Full number:
4532 0123 4567 8917
Where Luhn Is Used
| Application | Number Length | Example | Notes |
|---|---|---|---|
| Credit/debit cards | 13–19 digits | 4532012345678917 | Visa/MC/Amex/Discover all use Luhn |
| IMEI (phones) | 15 digits | 356938035678804 | Last digit is Luhn check |
| ICCID (SIM cards) | 19–20 digits | 89445012345678901234 | Starts with 89 (E.164 country code) |
| National provider IDs (US) | 10 digits | 1234567893 | NPI numbers use Luhn variant |
| Canadian SIN | 9 digits | 123456782 | Social Insurance Number |
| Gift card numbers | Variable | Varies by issuer | Most retail gift cards use Luhn |
| Routing numbers (US) | 9 digits | 021000021 | ABA routing uses a different algorithm (3-weight), not Luhn |
Credit Card Number Structure
4532 0123 4567 8917
|||| |||| |||| ||||
|||| |||| |||| |||└── Check digit (Luhn)
|||| |||| |||| ||└─── Account identifier (last 5 = check + account)
|||| |||| |||| └──── Account identifier
|||| |||| |||└────── Account identifier
|||| |||| ||└─────── Bank number
|||| |||| |└──────── Bank number
|||| |||| └───────── Bank number
|||| |||└─────────── Bank number
|||| ||└──────────── Bank number
|||| |└───────────── Issuer identifier
|||| └─────────────── Issuer identifier
|||└──────────────── Issuer identifier
||└───────────────── Major industry: 4 = Banking
|└────────────────── MII: 4 = Visa
└────────────────--- MII: 4 = Visa
Major Industry Identifier (MII)
| MII | Industry | Common Issuers |
|---|---|---|
| 1 | Airlines | Unused in practice |
| 2 | Airlines / Financial | MC (2221–2720 range) |
| 3 | Travel / Entertainment | Amex (34/37), JCB (35), Diners (30/36) |
| 4 | Banking | Visa |
| 5 | Banking | Mastercard (51–55) |
| 6 | Merchandising / Banking | Discover (6011/65), UnionPay (62) |
| 7 | Petroleum | Gas station cards |
| 8 | Telecommunications | Telecom cards |
| 9 | National / Reserved | Country-specific |
Code Implementations
Python
def luhn_validate(number: str) -> bool:
"""Validate a number using the Luhn algorithm."""
digits = [int(d) for d in number if d.isdigit()]
if len(digits) < 2:
return False
# Process from right to left
total = 0
for i, d in enumerate(reversed(digits)):
if i % 2 == 1: # Double every second digit from right
d = d * 2
if d > 9:
d -= 9
total += d
return total % 10 == 0
def luhn_check_digit(partial: str) -> int:
"""Compute the Luhn check digit for a partial number."""
digits = [int(d) for d in partial if d.isdigit()]
# Insert 0 as check digit placeholder
digits.append(0)
total = 0
for i, d in enumerate(reversed(digits)):
if i % 2 == 1:
d = d * 2
if d > 9:
d -= 9
total += d
return (10 - (total % 10)) % 10
# Test
print(luhn_validate("4532012345678917")) # True
print(luhn_check_digit("453201234567891")) # 7
JavaScript (Browser / Node)
function luhnValidate(number) {
const digits = number.replace(/\D/g, '');
if (digits.length < 2) return false;
let sum = 0;
let alternate = false;
for (let i = digits.length - 1; i >= 0; i--) {
let d = parseInt(digits[i], 10);
if (alternate) {
d *= 2;
if (d > 9) d -= 9;
}
sum += d;
alternate = !alternate;
}
return sum % 10 === 0;
}
function luhnCheckDigit(partial) {
return luhnValidate(partial + '0')
? 0
: 10 - ((computeSum(partial) + 0) % 10);
// Simplified: append 0, compute sum, return (10 - sum%10) % 10
}
// Test
console.log(luhnValidate('4532012345678917')); // true
C
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
bool luhn_validate(const char *number) {
int len = strlen(number);
int sum = 0;
bool alternate = false;
for (int i = len - 1; i >= 0; i--) {
if (number[i] < '0' || number[i] > '9') continue;
int d = number[i] - '0';
if (alternate) {
d *= 2;
if (d > 9) d -= 9;
}
sum += d;
alternate = !alternate;
}
return (sum % 10) == 0;
}
int main() {
printf("%d\\n", luhn_validate("4532012345678917")); // 1 (true)
return 0;
}
Java
public class Luhn {
public static boolean validate(String number) {
String digits = number.replaceAll("\\D", "");
if (digits.length() < 2) return false;
int sum = 0;
boolean alternate = false;
for (int i = digits.length() - 1; i >= 0; i--) {
int d = digits.charAt(i) - '0';
if (alternate) {
d *= 2;
if (d > 9) d -= 9;
}
sum += d;
alternate = !alternate;
}
return sum % 10 == 0;
}
public static void main(String[] args) {
System.out.println(validate("4532012345678917")); // true
}
}
Common Mistakes
When NOT to Use Luhn
| Use Case | Why Luhn Fails | Use Instead |
|---|---|---|
| Password hashing | Not a security function | bcrypt, scrypt, Argon2 |
| Message authentication | No key, trivially forgeable | HMAC-SHA256 |
| Detecting multi-digit errors | Only catches ~98% of errors | Damm algorithm, Verhoeff algorithm |
| Bank routing numbers (US ABA) | Different checksum scheme | 3-weight checksum (3x + 7x + x) |
| ISBN-13 | Uses different weights (1, 3 alternating) | ISBN-13 weighted checksum |
| UPC / EAN barcodes | Uses modulo-10 but different weights | EAN weighted checksum |
Luhn Error Detection Rates
| Error Type | Detection Rate | Notes |
|---|---|---|
| Single-digit error | 100% | All single-digit errors caught |
| Transposition of adjacent digits | ~97.7% | Fails for 09 ↔ 90 only |
| Jump transposition (a ↔ c) | ~0% | Not designed for this |
| Twin errors (aa → bb) | ~88.9% | Caught for most digit pairs |
| Phonetic errors (13 ↔ 30) | 100% | All phonetic errors caught |
| Random digit errors | ~10% | ~1 in 10 random strings pass |
IMEI Check Digit
IMEI (International Mobile Equipment Identity) numbers use Luhn for the 15th digit (check digit):
IMEI: 356938035678804
TAC: 35693803 (Type Allocation Code, identifies model)
SN: 567880 (Serial number)
CD: 4 (Luhn check digit of TAC+SN)
*#06# on any phone to display the IMEI. The 15th digit should pass Luhn validation. Some phones show a 16-digit IMEISV (software version) which replaces the check digit with a SVN (Software Version Number) — this does NOT pass Luhn.ICCID (SIM Card) Validation
ICCID (Integrated Circuit Card Identifier) is the 19–20 digit number on SIM cards. It starts with 89 (E.164 country code for telephony) and ends with a Luhn check digit:
ICCID: 8944 5012 3456 7890 1234
||| | | | | |
89 │ │ │ │ └── Luhn check digit
│ │ │ │ └────── Account code
│ │ │ └─────────── Account code
│ │ └──────────────── Issuer identifier
│ └───────────────────── Country code (44 = UK)
└────────────────────────── E.164 telephony country code
Summary
For related tools, see our CRC Calculator for error-detecting codes, HMAC Generator for message authentication, and Hash Digest Calculator for cryptographic hashing.