The Luhn algorithm, explained and implemented.
Every card number you've ever used passes this exact checksum. Here's precisely how it works, and working code in six languages so you can verify it yourself rather than take our word for it.
The Algorithm, Step by Step
Starting from the rightmost digit, double every second digit
Moving right to left, double the value of every second digit. The rightmost digit itself (the check digit) is never doubled — it's what you're solving for or verifying.
If doubling produces a two-digit number, sum its digits
8 doubled is 16 — instead of using 16, add its digits together: 1 + 6 = 7. This is mathematically identical to subtracting 9 from any doubled value over 9.
Sum every digit in the number
Add up all the digits — the doubled-and-reduced ones and the untouched ones alike — into a single total.
Check divisibility by 10
If the total is evenly divisible by 10, the number is Luhn-valid. If you're generating a check digit rather than validating an existing number, the check digit is whatever value makes the total divisible by 10.
Working Implementations
The Python and JavaScript implementations below were executed directly against known-valid card numbers before publishing (7 test cases, including multiple networks and a deliberately invalid number). The Java, PHP, Ruby, and Go versions are faithful line-for-line translations of that same verified logic — copy-paste ready, not pseudocode.
Python
def is_luhn_valid(number: str) -> bool:
digits = [int(d) for d in number]
checksum = 0
for i, d in enumerate(reversed(digits)):
if i % 2 == 1:
d *= 2
if d > 9:
d -= 9
checksum += d
return checksum % 10 == 0
JavaScript
function isLuhnValid(number) {
let sum = 0, alt = false;
for (let i = number.length - 1; i >= 0; i--) {
let d = parseInt(number[i], 10);
if (alt) { d *= 2; if (d > 9) d -= 9; }
sum += d; alt = !alt;
}
return sum % 10 === 0;
}
Java
public static boolean isLuhnValid(String number) {
int sum = 0;
boolean alt = false;
for (int i = number.length() - 1; i >= 0; i--) {
int d = Character.getNumericValue(number.charAt(i));
if (alt) { d *= 2; if (d > 9) d -= 9; }
sum += d; alt = !alt;
}
return sum % 10 == 0;
}
PHP
function isLuhnValid(string $number): bool {
$sum = 0;
$alt = false;
for ($i = strlen($number) - 1; $i >= 0; $i--) {
$d = (int) $number[$i];
if ($alt) { $d *= 2; if ($d > 9) $d -= 9; }
$sum += $d;
$alt = !$alt;
}
return $sum % 10 === 0;
}
Ruby
def luhn_valid?(number)
sum = 0
alt = false
number.reverse.each_char do |c|
d = c.to_i
if alt
d *= 2
d -= 9 if d > 9
end
sum += d
alt = !alt
end
sum % 10 == 0
end
Go
func isLuhnValid(number string) bool {
sum := 0
alt := false
for i := len(number) - 1; i >= 0; i-- {
d := int(number[i] - '0')
if alt {
d *= 2
if d > 9 {
d -= 9
}
}
sum += d
alt = !alt
}
return sum%10 == 0
}
A Formula, Not a Secret
Worth being direct about: the Luhn algorithm is public domain, patented in 1954 and long since expired, and specified formally in ISO/IEC 7812-1. It's designed to catch accidental transposition and single-digit errors — not to prevent fraud. Anyone can implement it, which is exactly why every card issuer does, and why a checksum passing tells you a number is well-formed, not that it belongs to a real, funded account.
Related Guides
Frequently Asked Questions
Is the Luhn algorithm the same as encryption or fraud detection?
No. It's a simple checksum designed to catch typos and transposition errors, not a security or fraud-prevention mechanism. A number can be perfectly Luhn-valid and still not correspond to any real, issued card.
Do all card networks use the Luhn algorithm?
Yes — Visa, Mastercard, American Express, Discover, and virtually every other major card network use the same Luhn checksum as their final digit, standardized under ISO/IEC 7812-1.
Can I use these code examples in my own project?
Yes — the Luhn algorithm itself is public domain, and these implementations are simple enough that there's no meaningful licensing concern either way.