Advertisement

Luhn Algorithm (Mod 10)

The checksum behind every credit card number, explained step by step. Check a number or calculate its check digit below, then copy a tested implementation in your language.

Advertisement

Check tests a complete number. Check digit treats your input as the number without its last digit and calculates the digit to append.

Advertisement

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

  1. Start with the rightmost digit (the check digit) and move left.
  2. Double every second digit: the second from the right, the fourth, the sixth, and so on.
  3. 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.
  4. Add up all the digits, doubled and not doubled.
  5. If the total is a multiple of 10 (ends in 0), the number is valid.
Advertisement

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):

Digit7992739871
Doubled?–18–4–6–16–2
Value added7994769772

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:

Digit79927398713
Doubled?–18–4–6–16–2–
Value added79947697723

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

ErrorExampleDetected?
One wrong digit4111 → 4115Always
Two neighbouring digits swapped12 → 21Yes, except 09 ↔ 90
Twin error22 → 55, 33 → 66, 44 → 77No
Several wrong digits4111 → 4220Only 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

Where the Luhn algorithm is used

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.

Related tools

Advertisement