What is the Luhn algorithm?
The Luhn algorithm, also called the mod 10 or modulus 10 algorithm, is a simple checksum used to detect typing mistakes in identification numbers. It was invented by IBM scientist Hans Peter Luhn, who filed the patent in 1954 (US 2,950,048, granted in 1960). Today it is part of ISO/IEC 7812, the standard for card numbers, so the last digit of every Visa, Mastercard, American Express, Discover and JCB number is a Luhn check digit.
The algorithm is not encryption and offers no security. Anyone can calculate a valid check digit — that is exactly how test card generators work. Its only job is to catch accidental errors before a number is sent anywhere, so a checkout form can say “check your card number” instantly instead of waiting for the bank to decline.
How the Luhn check works
- Start with the rightmost digit (the check digit) and move left.
- Double every second digit: the second from the right, the fourth, the sixth, and so on.
- If doubling gives a two-digit number, add its digits — or, equivalently, subtract 9. So 7 × 2 = 14 becomes 5, and 9 × 2 = 18 becomes 9.
- Add up all the digits, doubled and not doubled.
- If the total is a multiple of 10 (ends in 0), the number is valid.
Worked example: calculating a check digit
Take the payload 7992739871 and find the digit to append. Because the new check digit will be in the rightmost position, the doubling starts with the last digit of the payload (highlighted):
| Digit | 7 | 9 | 9 | 2 | 7 | 3 | 9 | 8 | 7 | 1 |
|---|---|---|---|---|---|---|---|---|---|---|
| Doubled? | – | 18 | – | 4 | – | 6 | – | 16 | – | 2 |
| Value added | 7 | 9 | 9 | 4 | 7 | 6 | 9 | 7 | 7 | 2 |
The values add up to 67. The next multiple of 10 is 70, so the check digit is 70 − 67 = 3. In one formula: check = (10 − (sum mod 10)) mod 10. The full number is 79927398713.
Worked example: validating a number
To validate 79927398713, run the same steps over all of its digits, this time starting the doubling with the second digit from the right:
| Digit | 7 | 9 | 9 | 2 | 7 | 3 | 9 | 8 | 7 | 1 | 3 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Doubled? | – | 18 | – | 4 | – | 6 | – | 16 | – | 2 | – |
| Value added | 7 | 9 | 9 | 4 | 7 | 6 | 9 | 7 | 7 | 2 | 3 |
The total is 70, which ends in 0, so the number passes. Change any single digit and the total changes by 1 to 9, so it no longer ends in 0. Try it in the calculator above.
Luhn algorithm code
Each implementation below is tested against the same examples (4111 1111 1111 1111 is valid; the check digit for 7992739871 is 3) and against thousands of random numbers. They work on strings, so leading zeros are kept and long numbers don’t overflow.
def luhn_checksum(number: str) -> int:
"""Luhn sum mod 10 of a digit string; 0 means the number is valid."""
total = 0
for i, ch in enumerate(reversed(number)):
d = int(ch)
if i % 2 == 1: # every second digit from the right
d *= 2
if d > 9:
d -= 9
total += d
return total % 10
def is_luhn_valid(number: str) -> bool:
digits = number.replace(" ", "").replace("-", "")
return digits.isascii() and digits.isdigit() and len(digits) > 1 and luhn_checksum(digits) == 0
def luhn_check_digit(payload: str) -> int:
"""Digit to append to payload so that the full number passes Luhn."""
return (10 - luhn_checksum(payload + "0")) % 10
print(is_luhn_valid("4111 1111 1111 1111")) # True
print(luhn_check_digit("7992739871")) # 3
function luhnChecksum(number) {
let sum = 0;
for (let i = 0; i < number.length; i++) {
let d = Number(number[number.length - 1 - i]);
if (i % 2 === 1) { // every second digit from the right
d *= 2;
if (d > 9) d -= 9;
}
sum += d;
}
return sum % 10; // 0 means valid
}
function isLuhnValid(number) {
const digits = String(number).replace(/[\s-]/g, '');
return /^\d{2,}$/.test(digits) && luhnChecksum(digits) === 0;
}
function luhnCheckDigit(payload) {
return (10 - luhnChecksum(payload + '0')) % 10;
}
console.log(isLuhnValid('4111 1111 1111 1111')); // true
console.log(luhnCheckDigit('7992739871')); // 3
<?php
function luhn_checksum(string $number): int
{
$sum = 0;
$len = strlen($number);
for ($i = 0; $i < $len; $i++) {
$d = (int) $number[$len - 1 - $i];
if ($i % 2 === 1) { // every second digit from the right
$d *= 2;
if ($d > 9) {
$d -= 9;
}
}
$sum += $d;
}
return $sum % 10; // 0 means valid
}
function is_luhn_valid(string $number): bool
{
$digits = preg_replace('/[\s-]/', '', $number);
return preg_match('/^[0-9]{2,}$/D', $digits) === 1 && luhn_checksum($digits) === 0;
}
function luhn_check_digit(string $payload): int
{
return (10 - luhn_checksum($payload . '0')) % 10;
}
var_dump(is_luhn_valid('4111 1111 1111 1111')); // bool(true)
echo luhn_check_digit('7992739871'), PHP_EOL; // 3
public final class Luhn {
/** Luhn sum mod 10 of a digit string; 0 means the number is valid. */
static int checksum(String number) {
int sum = 0;
for (int i = 0; i < number.length(); i++) {
int d = number.charAt(number.length() - 1 - i) - '0';
if (i % 2 == 1) { // every second digit from the right
d *= 2;
if (d > 9) d -= 9;
}
sum += d;
}
return sum % 10;
}
static boolean isValid(String number) {
String digits = number.replaceAll("[\\s-]", "");
return digits.matches("[0-9]{2,}") && checksum(digits) == 0;
}
static int checkDigit(String payload) {
return (10 - checksum(payload + "0")) % 10;
}
public static void main(String[] args) {
System.out.println(isValid("4111 1111 1111 1111")); // true
System.out.println(checkDigit("7992739871")); // 3
}
}
using System;
using System.Text.RegularExpressions;
public static class Luhn
{
// Luhn sum mod 10 of a digit string; 0 means the number is valid.
public static int Checksum(string number)
{
int sum = 0;
for (int i = 0; i < number.Length; i++)
{
int d = number[number.Length - 1 - i] - '0';
if (i % 2 == 1) // every second digit from the right
{
d *= 2;
if (d > 9) d -= 9;
}
sum += d;
}
return sum % 10;
}
public static bool IsValid(string number)
{
string digits = Regex.Replace(number, @"[\s-]", "");
return Regex.IsMatch(digits, @"^[0-9]{2,}$") && Checksum(digits) == 0;
}
public static int CheckDigit(string payload) => (10 - Checksum(payload + "0")) % 10;
public static void Main()
{
Console.WriteLine(IsValid("4111 1111 1111 1111")); // True
Console.WriteLine(CheckDigit("7992739871")); // 3
}
}
package main
import (
"fmt"
"regexp"
"strings"
)
// luhnChecksum returns the Luhn sum mod 10 of a digit string; 0 means valid.
func luhnChecksum(number string) int {
sum := 0
for i := 0; i < len(number); i++ {
d := int(number[len(number)-1-i] - '0')
if i%2 == 1 { // every second digit from the right
d *= 2
if d > 9 {
d -= 9
}
}
sum += d
}
return sum % 10
}
var digitsOnly = regexp.MustCompile(`^[0-9]{2,}$`)
func isLuhnValid(number string) bool {
digits := strings.NewReplacer(" ", "", "-", "").Replace(number)
return digitsOnly.MatchString(digits) && luhnChecksum(digits) == 0
}
func luhnCheckDigit(payload string) int {
return (10 - luhnChecksum(payload+"0")) % 10
}
func main() {
fmt.Println(isLuhnValid("4111 1111 1111 1111")) // true
fmt.Println(luhnCheckDigit("7992739871")) // 3
}
What the Luhn check catches — and what it misses
| Error | Example | Detected? |
|---|---|---|
| One wrong digit | 4111 → 4115 | Always |
| Two neighbouring digits swapped | 12 → 21 | Yes, except 09 ↔ 90 |
| Twin error | 22 → 55, 33 → 66, 44 → 77 | No |
| Several wrong digits | 4111 → 4220 | Only sometimes (about 9 in 10) |
| Random or made-up number | — | 1 in 10 passes by chance |
For stronger protection, other systems use different checksums: IBANs use mod 97, which catches all single errors and almost all transpositions, and the Verhoeff and Damm algorithms catch every adjacent swap. Card numbers kept Luhn because it is cheap to compute — it was designed to run on mechanical devices.
Common implementation mistakes
- Doubling from the left. It only works by accident for numbers with an even length. Always count from the right.
- Parsing the number as an integer. A 19-digit card number overflows 64-bit integers in some languages, and leading zeros disappear. Work on the string of digits.
- Not normalising input. Strip spaces and dashes first, then reject anything that is not a digit. Beware of regex
\d, which matches non-ASCII digits in some languages. - Treating Luhn as the whole validation. A good form also checks the brand prefix and length — see the card number format guide — and leaves the rest to the payment gateway.
Where the Luhn algorithm is used
- Credit, debit and prepaid card numbers (ISO/IEC 7812)
- IMEI numbers of mobile phones
- Canadian Social Insurance Numbers
- US National Provider Identifiers in healthcare
- Many loyalty, gift card and account numbers chosen by companies
Frequently asked questions
What is the Luhn algorithm used for?
It detects typing mistakes in identification numbers. Credit and debit card numbers, IMEI numbers of mobile phones, Canadian Social Insurance Numbers and US National Provider Identifiers all end with a Luhn check digit.
Does passing the Luhn check mean a card is real?
No. About one random number in ten passes. The check only shows that the number was typed without the common mistakes; whether a card exists is known only to the issuing bank.
Which errors does Luhn not detect?
Swapping 09 and 90, and the “twin” errors 22↔55, 33↔66 and 44↔77. It also cannot detect most errors that change two or more digits.
Is Luhn the same as mod 10?
Yes, “mod 10” and “modulus 10” are common names for the Luhn formula, because a number is valid when the weighted digit sum is divisible by 10.
Why double from the right and not from the left?
Counting from the right means the check digit is never doubled, whatever the length of the number. If you count from the left, 15-digit and 16-digit numbers would be doubled differently and valid numbers would fail.